From f6ce926719943751cf65cacde7fae050593eb2d6 Mon Sep 17 00:00:00 2001 From: Perberos Date: Wed, 9 Nov 2011 18:17:43 -0300 Subject: inicial --- AUTHORS | 12 + COPYING | 340 + MAINTAINERS | 11 + Makefile.am | 116 + NEWS | 2810 +++++++ README | 21 + TODO | 3 + autogen.sh | 23 + backend/Makefile.am | 40 + backend/backend.symbols | 1 + backend/comics/Makefile.am | 32 + backend/comics/comics-document.c | 936 +++ backend/comics/comics-document.h | 38 + backend/comics/comicsdocument.evince-backend.in | 4 + backend/djvu/Makefile.am | 38 + backend/djvu/djvu-document-private.h | 48 + backend/djvu/djvu-document.c | 706 ++ backend/djvu/djvu-document.h | 38 + backend/djvu/djvu-links.c | 434 ++ backend/djvu/djvu-links.h | 35 + backend/djvu/djvu-text-page.c | 445 ++ backend/djvu/djvu-text-page.h | 59 + backend/djvu/djvudocument.evince-backend.in | 4 + backend/dvi/Makefile.am | 47 + backend/dvi/cairo-device.c | 373 + backend/dvi/cairo-device.h | 41 + backend/dvi/dvi-document.c | 610 ++ backend/dvi/dvi-document.h | 38 + backend/dvi/dvidocument.evince-backend.in | 4 + backend/dvi/fonts.c | 57 + backend/dvi/fonts.h | 6 + backend/dvi/mdvi-lib/Makefile.am | 43 + backend/dvi/mdvi-lib/afmparse.c | 1303 ++++ backend/dvi/mdvi-lib/afmparse.h | 307 + backend/dvi/mdvi-lib/bitmap.c | 1035 +++ backend/dvi/mdvi-lib/bitmap.h | 145 + backend/dvi/mdvi-lib/color.c | 136 + backend/dvi/mdvi-lib/color.h | 34 + backend/dvi/mdvi-lib/common.c | 168 + backend/dvi/mdvi-lib/common.h | 285 + backend/dvi/mdvi-lib/defaults.h | 71 + backend/dvi/mdvi-lib/dviopcodes.h | 72 + backend/dvi/mdvi-lib/dviread.c | 1585 ++++ backend/dvi/mdvi-lib/files.c | 80 + backend/dvi/mdvi-lib/font.c | 519 ++ backend/dvi/mdvi-lib/fontmap.c | 1174 +++ backend/dvi/mdvi-lib/fontmap.h | 82 + backend/dvi/mdvi-lib/fontsrch.c | 371 + backend/dvi/mdvi-lib/gf.c | 395 + backend/dvi/mdvi-lib/hash.c | 224 + backend/dvi/mdvi-lib/hash.h | 49 + backend/dvi/mdvi-lib/list.c | 120 + backend/dvi/mdvi-lib/mdvi.h | 623 ++ backend/dvi/mdvi-lib/pagesel.c | 491 ++ backend/dvi/mdvi-lib/paper.c | 171 + backend/dvi/mdvi-lib/paper.h | 32 + backend/dvi/mdvi-lib/pk.c | 570 ++ backend/dvi/mdvi-lib/private.h | 65 + backend/dvi/mdvi-lib/setup.c | 48 + backend/dvi/mdvi-lib/sp-epsf.c | 311 + backend/dvi/mdvi-lib/special.c | 249 + backend/dvi/mdvi-lib/sysdeps.h | 116 + backend/dvi/mdvi-lib/t1.c | 630 ++ backend/dvi/mdvi-lib/tfm.c | 214 + backend/dvi/mdvi-lib/tfmfile.c | 747 ++ backend/dvi/mdvi-lib/tt.c | 495 ++ backend/dvi/mdvi-lib/util.c | 550 ++ backend/dvi/mdvi-lib/vf.c | 241 + backend/dvi/texmfcnf.c | 64 + backend/dvi/texmfcnf.h | 34 + backend/impress/Makefile.am | 57 + backend/impress/common.h | 40 + backend/impress/document.c | 140 + backend/impress/f_oasis.c | 170 + backend/impress/f_oo13.c | 181 + backend/impress/iksemel.c | 1882 +++++ backend/impress/iksemel.h | 402 + backend/impress/imposter.h | 84 + backend/impress/impress-document.c | 548 ++ backend/impress/impress-document.h | 39 + backend/impress/impressdocument.evince-backend.in | 4 + backend/impress/internal.h | 85 + backend/impress/r_back.c | 46 + backend/impress/r_draw.c | 120 + backend/impress/r_geometry.c | 208 + backend/impress/r_gradient.c | 387 + backend/impress/r_style.c | 111 + backend/impress/r_text.c | 386 + backend/impress/render.c | 54 + backend/impress/zip.c | 349 + backend/impress/zip.h | 18 + backend/pdf/Makefile.am | 34 + backend/pdf/ev-poppler.cc | 3290 ++++++++ backend/pdf/ev-poppler.h | 40 + backend/pdf/pdfdocument.evince-backend.in | 6 + backend/pixbuf/Makefile.am | 30 + backend/pixbuf/pixbuf-document.c | 208 + backend/pixbuf/pixbuf-document.h | 38 + backend/pixbuf/pixbufdocument.evince-backend.in | 4 + backend/ps/Makefile.am | 32 + backend/ps/ev-spectre.c | 473 ++ backend/ps/ev-spectre.h | 48 + backend/ps/psdocument.evince-backend.in | 5 + backend/tiff/Makefile.am | 33 + backend/tiff/tiff-document.c | 533 ++ backend/tiff/tiff-document.h | 38 + backend/tiff/tiff2ps.c | 1872 +++++ backend/tiff/tiff2ps.h | 30 + backend/tiff/tiffdocument.evince-backend.in | 4 + config.h.in~ | 191 + configure.ac | 884 +++ cut-n-paste/Makefile.am | 3 + cut-n-paste/gimpcellrenderertoggle/Makefile.am | 38 + .../gimpcellrenderertoggle.c | 492 ++ .../gimpcellrenderertoggle.h | 77 + .../gimpcellrenderertoggle/gimpwidgetsmarshal.list | 26 + cut-n-paste/smclient/Makefile.am | 42 + cut-n-paste/smclient/eggdesktopfile.c | 1477 ++++ cut-n-paste/smclient/eggdesktopfile.h | 159 + cut-n-paste/smclient/eggsmclient-osx.c | 235 + cut-n-paste/smclient/eggsmclient-private.h | 53 + cut-n-paste/smclient/eggsmclient-win32.c | 353 + cut-n-paste/smclient/eggsmclient-xsmp.c | 1370 ++++ cut-n-paste/smclient/eggsmclient.c | 589 ++ cut-n-paste/smclient/eggsmclient.h | 117 + cut-n-paste/synctex/Makefile.am | 14 + cut-n-paste/synctex/synctex_parser.c | 4171 +++++++++++ cut-n-paste/synctex/synctex_parser.h | 345 + cut-n-paste/synctex/synctex_parser_utils.c | 462 ++ cut-n-paste/synctex/synctex_parser_utils.h | 123 + cut-n-paste/toolbar-editor/Makefile.am | 108 + cut-n-paste/toolbar-editor/egg-editable-toolbar.c | 1828 +++++ cut-n-paste/toolbar-editor/egg-editable-toolbar.h | 91 + cut-n-paste/toolbar-editor/egg-toolbar-editor.c | 672 ++ cut-n-paste/toolbar-editor/egg-toolbar-editor.h | 63 + cut-n-paste/toolbar-editor/egg-toolbars-model.c | 987 +++ cut-n-paste/toolbar-editor/egg-toolbars-model.h | 190 + cut-n-paste/toolbar-editor/eggmarshalers.list | 1 + cut-n-paste/totem-screensaver/Makefile.am | 16 + cut-n-paste/totem-screensaver/README | 3 + cut-n-paste/totem-screensaver/totem-scrsaver.c | 554 ++ cut-n-paste/totem-screensaver/totem-scrsaver.h | 57 + cut-n-paste/zoom-control/Makefile.am | 16 + cut-n-paste/zoom-control/ephy-zoom-action.c | 336 + cut-n-paste/zoom-control/ephy-zoom-action.h | 68 + cut-n-paste/zoom-control/ephy-zoom-control.c | 345 + cut-n-paste/zoom-control/ephy-zoom-control.h | 63 + cut-n-paste/zoom-control/ephy-zoom.c | 68 + cut-n-paste/zoom-control/ephy-zoom.h | 81 + data/Makefile.am | 109 + data/evince-previewer-ui.xml | 34 + data/evince-toolbar.xml | 39 + data/evince-ui.xml | 138 + data/evince.1 | 41 + data/evince.convert | 2 + data/evince.desktop.in.in | 17 + data/evince.ico | Bin 0 -> 24998 bytes data/hand-open.png | Bin 0 -> 1088 bytes data/icons/16x16/Makefile.am | 3 + data/icons/16x16/actions/Makefile.am | 27 + data/icons/16x16/actions/object-rotate-left.png | Bin 0 -> 607 bytes data/icons/16x16/actions/object-rotate-right.png | Bin 0 -> 632 bytes data/icons/16x16/actions/view-page-continuous.png | Bin 0 -> 446 bytes data/icons/16x16/actions/view-page-continuous.xcf | Bin 0 -> 2190 bytes data/icons/16x16/actions/view-page-facing.png | Bin 0 -> 471 bytes data/icons/16x16/actions/view-page-facing.xcf | Bin 0 -> 2158 bytes data/icons/16x16/actions/zoom-fit-height.png | Bin 0 -> 811 bytes data/icons/16x16/actions/zoom-fit-page.svg | 282 + data/icons/16x16/actions/zoom-fit-width.png | Bin 0 -> 771 bytes data/icons/16x16/actions/zoom-fit-width.svg | 278 + data/icons/16x16/actions/zoom.png | Bin 0 -> 876 bytes data/icons/16x16/actions/zoom.svg | 247 + data/icons/16x16/apps/Makefile.am | 16 + data/icons/16x16/apps/evince.png | Bin 0 -> 840 bytes data/icons/16x16/mimetypes/Makefile.am | 14 + .../16x16/mimetypes/x-office-presentation.png | Bin 0 -> 668 bytes data/icons/22x22/Makefile.am | 3 + data/icons/22x22/actions/Makefile.am | 28 + data/icons/22x22/actions/eye.png | Bin 0 -> 617 bytes data/icons/22x22/actions/object-rotate-left.png | Bin 0 -> 911 bytes data/icons/22x22/actions/object-rotate-right.png | Bin 0 -> 939 bytes data/icons/22x22/actions/view-page-continuous.png | Bin 0 -> 722 bytes data/icons/22x22/actions/view-page-continuous.xcf | Bin 0 -> 4004 bytes data/icons/22x22/actions/view-page-facing.png | Bin 0 -> 483 bytes data/icons/22x22/actions/view-page-facing.xcf | Bin 0 -> 1871 bytes data/icons/22x22/actions/zoom-fit-page.png | Bin 0 -> 1377 bytes data/icons/22x22/actions/zoom-fit-page.svg | 254 + data/icons/22x22/actions/zoom-fit-width.png | Bin 0 -> 1405 bytes data/icons/22x22/actions/zoom-fit-width.svg | 252 + data/icons/22x22/actions/zoom.png | Bin 0 -> 1294 bytes data/icons/22x22/actions/zoom.svg | 215 + data/icons/22x22/apps/Makefile.am | 16 + data/icons/22x22/apps/evince.png | Bin 0 -> 1209 bytes data/icons/22x22/mimetypes/Makefile.am | 14 + .../22x22/mimetypes/x-office-presentation.png | Bin 0 -> 1024 bytes data/icons/24x24/Makefile.am | 3 + data/icons/24x24/actions/Makefile.am | 23 + data/icons/24x24/actions/object-rotate-left.png | Bin 0 -> 924 bytes data/icons/24x24/actions/object-rotate-right.png | Bin 0 -> 943 bytes data/icons/24x24/actions/stock_filters-invert.png | Bin 0 -> 402 bytes data/icons/24x24/actions/view-page-continuous.png | Bin 0 -> 724 bytes data/icons/24x24/actions/view-page-facing.png | Bin 0 -> 471 bytes data/icons/24x24/actions/zoom-fit-height.png | Bin 0 -> 1146 bytes data/icons/24x24/actions/zoom-fit-width.png | Bin 0 -> 1170 bytes data/icons/24x24/actions/zoom.png | Bin 0 -> 1344 bytes data/icons/24x24/apps/Makefile.am | 16 + data/icons/24x24/apps/evince.png | Bin 0 -> 1226 bytes data/icons/24x24/mimetypes/Makefile.am | 14 + .../24x24/mimetypes/x-office-presentation.png | Bin 0 -> 1643 bytes data/icons/32x32/Makefile.am | 3 + data/icons/32x32/actions/Makefile.am | 19 + data/icons/32x32/actions/object-rotate-left.png | Bin 0 -> 1319 bytes data/icons/32x32/actions/object-rotate-right.png | Bin 0 -> 1353 bytes data/icons/32x32/actions/view-page-continuous.png | Bin 0 -> 849 bytes data/icons/32x32/actions/view-page-facing.png | Bin 0 -> 943 bytes data/icons/32x32/mimetypes/Makefile.am | 14 + .../32x32/mimetypes/x-office-presentation.png | Bin 0 -> 1618 bytes data/icons/48x48/Makefile.am | 4 + data/icons/48x48/actions/Makefile.am | 20 + data/icons/48x48/actions/close.png | Bin 0 -> 363 bytes data/icons/48x48/actions/resize-se.png | Bin 0 -> 312 bytes data/icons/48x48/actions/resize-sw.png | Bin 0 -> 295 bytes data/icons/48x48/actions/view-page-continuous.png | Bin 0 -> 1494 bytes data/icons/48x48/actions/view-page-facing.png | Bin 0 -> 1485 bytes data/icons/48x48/apps/Makefile.am | 16 + data/icons/48x48/apps/evince.png | Bin 0 -> 3314 bytes data/icons/Makefile.am | 4 + data/icons/scalable/Makefile.am | 3 + data/icons/scalable/actions/Makefile.am | 15 + data/icons/scalable/actions/object-rotate-left.svg | 208 + .../icons/scalable/actions/object-rotate-right.svg | 210 + data/icons/scalable/apps/Makefile.am | 16 + data/icons/scalable/apps/evince.svg | 500 ++ data/icons/scalable/mimetypes/Makefile.am | 14 + .../scalable/mimetypes/x-office-presentation.svg | 686 ++ data/org.mate.Evince.gschema.xml.in | 51 + data/org.mate.evince.Daemon.service.in | 3 + evince-document.h | 62 + evince-document.pc.in | 12 + evince-view.h | 34 + evince-view.pc.in | 11 + help/C/evince.xml | 774 ++ help/C/figures/evince_start_window.png | Bin 0 -> 17271 bytes help/C/legal.xml | 76 + help/ChangeLog | 362 + help/Makefile.am | 17 + help/bg/bg.po | 1015 +++ help/bg/figures/evince_start_window.png | Bin 0 -> 22691 bytes help/ca/ca.po | 1281 ++++ help/ca/figures/evince_start_window.png | Bin 0 -> 15084 bytes help/cs/cs.po | 1257 ++++ help/cs/figures/evince_start_window.png | Bin 0 -> 16696 bytes help/de/de.po | 1275 ++++ help/de/figures/evince_start_window.png | Bin 0 -> 12327 bytes help/el/el.po | 943 +++ help/el/figures/evince_start_window.png | Bin 0 -> 21766 bytes help/en_GB/en_GB.po | 1256 ++++ help/es/es.po | 1281 ++++ help/es/figures/evince_start_window.png | Bin 0 -> 16965 bytes help/eu/eu.po | 626 ++ help/eu/figures/evince_start_window.png | Bin 0 -> 18535 bytes help/evince.omf.in | 11 + help/fi/fi.po | 1244 ++++ help/fi/figures/evince_start_window.png | Bin 0 -> 16351 bytes help/fr/figures/evince_start_window.png | Bin 0 -> 15794 bytes help/fr/fr.po | 1297 ++++ help/it/figures/evince_start_window.png | Bin 0 -> 13779 bytes help/it/it.po | 1285 ++++ help/ja/figures/evince_start_window.png | Bin 0 -> 16465 bytes help/ja/ja.po | 948 +++ help/nl/figures/evince_start_window.png | Bin 0 -> 10972 bytes help/nl/nl.po | 929 +++ help/oc/oc.po | 817 ++ help/pt_BR/pt_BR.po | 1272 ++++ help/reference/Makefile.am | 2 + help/reference/libdocument/Makefile.am | 132 + help/reference/libdocument/libevdocument-docs.xml | 191 + .../libdocument/libevdocument-overrides.txt | 0 .../libdocument/libevdocument-sections.txt | 781 ++ help/reference/libdocument/libevdocument.types | 55 + help/reference/libdocument/version.xml.in | 1 + help/reference/libview/Makefile.am | 139 + help/reference/libview/libevview-docs.xml | 145 + help/reference/libview/libevview-overrides.txt | 0 help/reference/libview/libevview-sections.txt | 338 + help/reference/libview/libevview.types | 29 + help/reference/libview/version.xml.in | 1 + help/reference/shell/Makefile.am | 150 + help/reference/shell/evince-docs.xml | 163 + help/reference/shell/evince-overrides.txt | 0 help/reference/shell/evince-sections.txt | 472 ++ help/reference/shell/evince.types | 24 + help/reference/shell/version.xml.in | 1 + help/ru/ru.po | 1238 ++++ help/sl/sl.po | 906 +++ help/sr/sr.po | 1626 ++++ help/sv/figures/evince_start_window.png | Bin 0 -> 17118 bytes help/sv/sv.po | 900 +++ help/uk/figures/evince_start_window.png | Bin 0 -> 14049 bytes help/uk/uk.po | 998 +++ help/vi/vi.po | 898 +++ help/zh_CN/zh_CN.po | 1183 +++ libdocument/Makefile.am | 193 + libdocument/ev-annotation.c | 1002 +++ libdocument/ev-annotation.h | 161 + libdocument/ev-async-renderer.c | 65 + libdocument/ev-async-renderer.h | 65 + libdocument/ev-attachment.c | 439 ++ libdocument/ev-attachment.h | 79 + libdocument/ev-backends-manager.c | 333 + libdocument/ev-backends-manager.h | 48 + libdocument/ev-debug.c | 177 + libdocument/ev-debug.h | 106 + libdocument/ev-document-annotations.c | 75 + libdocument/ev-document-annotations.h | 99 + libdocument/ev-document-attachments.c | 49 + libdocument/ev-document-attachments.h | 59 + libdocument/ev-document-factory.c | 397 + libdocument/ev-document-factory.h | 39 + libdocument/ev-document-find.c | 42 + libdocument/ev-document-find.h | 65 + libdocument/ev-document-fonts.c | 59 + libdocument/ev-document-fonts.h | 78 + libdocument/ev-document-forms.c | 161 + libdocument/ev-document-forms.h | 128 + libdocument/ev-document-images.c | 47 + libdocument/ev-document-images.h | 65 + libdocument/ev-document-info.h | 154 + libdocument/ev-document-layers.c | 74 + libdocument/ev-document-layers.h | 85 + libdocument/ev-document-links.c | 129 + libdocument/ev-document-links.h | 87 + libdocument/ev-document-misc.c | 400 + libdocument/ev-document-misc.h | 67 + libdocument/ev-document-print.c | 41 + libdocument/ev-document-print.h | 59 + libdocument/ev-document-security.c | 48 + libdocument/ev-document-security.h | 67 + libdocument/ev-document-text.c | 70 + libdocument/ev-document-text.h | 75 + libdocument/ev-document-thumbnails.c | 63 + libdocument/ev-document-thumbnails.h | 69 + libdocument/ev-document-transition.c | 57 + libdocument/ev-document-transition.h | 64 + libdocument/ev-document-type-builtins.c.template | 44 + libdocument/ev-document-type-builtins.h.template | 29 + libdocument/ev-document.c | 867 +++ libdocument/ev-document.h | 303 + libdocument/ev-file-exporter.c | 84 + libdocument/ev-file-exporter.h | 101 + libdocument/ev-file-helpers.c | 683 ++ libdocument/ev-file-helpers.h | 71 + libdocument/ev-form-field.c | 209 + libdocument/ev-form-field.h | 214 + libdocument/ev-image.c | 181 + libdocument/ev-image.h | 68 + libdocument/ev-init.c | 154 + libdocument/ev-init.h | 41 + libdocument/ev-layer.c | 86 + libdocument/ev-layer.h | 62 + libdocument/ev-link-action.c | 341 + libdocument/ev-link-action.h | 73 + libdocument/ev-link-dest.c | 511 ++ libdocument/ev-link-dest.h | 95 + libdocument/ev-link.c | 202 + libdocument/ev-link.h | 55 + libdocument/ev-mapping-list.c | 146 + libdocument/ev-mapping-list.h | 53 + libdocument/ev-module.c | 196 + libdocument/ev-module.h | 74 + libdocument/ev-page.c | 62 + libdocument/ev-page.h | 63 + libdocument/ev-render-context.c | 101 + libdocument/ev-render-context.h | 70 + libdocument/ev-selection.c | 77 + libdocument/ev-selection.h | 93 + libdocument/ev-transition-effect.c | 224 + libdocument/ev-transition-effect.h | 87 + libdocument/ev-version.h.in | 69 + libmisc/Makefile.am | 22 + libmisc/ev-page-action-widget.c | 495 ++ libmisc/ev-page-action-widget.h | 46 + libmisc/ev-page-action.c | 239 + libmisc/ev-page-action.h | 68 + libview/Makefile.am | 163 + libview/ev-annotation-window.c | 654 ++ libview/ev-annotation-window.h | 57 + libview/ev-document-model.c | 571 ++ libview/ev-document-model.h | 87 + libview/ev-job-scheduler.c | 305 + libview/ev-job-scheduler.h | 48 + libview/ev-jobs.c | 1577 ++++ libview/ev-jobs.h | 502 ++ libview/ev-loading-window.c | 278 + libview/ev-loading-window.h | 49 + libview/ev-page-cache.c | 521 ++ libview/ev-page-cache.h | 71 + libview/ev-pixbuf-cache.c | 1192 +++ libview/ev-pixbuf-cache.h | 89 + libview/ev-print-operation.c | 2028 +++++ libview/ev-print-operation.h | 71 + libview/ev-stock-icons.c | 143 + libview/ev-stock-icons.h | 57 + libview/ev-timeline.c | 450 ++ libview/ev-timeline.h | 90 + libview/ev-transition-animation.c | 676 ++ libview/ev-transition-animation.h | 73 + libview/ev-view-accessible.c | 911 +++ libview/ev-view-accessible.h | 33 + libview/ev-view-cursor.c | 55 + libview/ev-view-cursor.h | 47 + libview/ev-view-marshal.list | 2 + libview/ev-view-presentation.c | 1498 ++++ libview/ev-view-presentation.h | 51 + libview/ev-view-private.h | 225 + libview/ev-view-type-builtins.c.template | 44 + libview/ev-view-type-builtins.h.template | 29 + libview/ev-view.c | 6274 ++++++++++++++++ libview/ev-view.h | 115 + m4/gtk-doc.m4 | 61 + m4/intltool.m4 | 216 + m4/libtool.m4 | 7831 ++++++++++++++++++++ m4/ltoptions.m4 | 369 + m4/ltsugar.m4 | 123 + m4/ltversion.m4 | 23 + m4/lt~obsolete.m4 | 98 + m4/mate-doc-utils.m4 | 56 + po/ChangeLog | 4424 +++++++++++ po/LINGUAS | 85 + po/POTFILES.in | 66 + po/POTFILES.skip | 16 + po/af.po | 1423 ++++ po/ar.po | 1835 +++++ po/as.po | 1624 ++++ po/ast.po | 1813 +++++ po/be.po | 1354 ++++ po/be@latin.po | 1428 ++++ po/bg.po | 1723 +++++ po/bn.po | 1813 +++++ po/bn_IN.po | 1647 ++++ po/br.po | 1475 ++++ po/ca.po | 2211 ++++++ po/ca@valencia.po | 2211 ++++++ po/cs.po | 1562 ++++ po/cy.po | 1115 +++ po/da.po | 2155 ++++++ po/de.po | 1812 +++++ po/dz.po | 1603 ++++ po/el.po | 1709 +++++ po/en@shaw.po | 1537 ++++ po/en_CA.po | 1260 ++++ po/en_GB.po | 2359 ++++++ po/eo.po | 1648 ++++ po/es.po | 1853 +++++ po/et.po | 1344 ++++ po/eu.po | 1722 +++++ po/fa.po | 1108 +++ po/fi.po | 1764 +++++ po/fr.po | 1747 +++++ po/ga.po | 1462 ++++ po/gl.po | 1757 +++++ po/gu.po | 1740 +++++ po/he.po | 1719 +++++ po/hi.po | 1459 ++++ po/hu.po | 1694 +++++ po/id.po | 1717 +++++ po/it.po | 1742 +++++ po/ja.po | 1739 +++++ po/ka.po | 1251 ++++ po/kk.po | 1672 +++++ po/kn.po | 1620 ++++ po/ko.po | 1707 +++++ po/ks.po | 1393 ++++ po/ku.po | 1287 ++++ po/lt.po | 1818 +++++ po/lv.po | 1783 +++++ po/mai.po | 1404 ++++ po/mg.po | 1379 ++++ po/mk.po | 1331 ++++ po/ml.po | 1638 ++++ po/mn.po | 1601 ++++ po/mr.po | 1711 +++++ po/ms.po | 1680 +++++ po/nb.po | 1710 +++++ po/nds.po | 1464 ++++ po/ne.po | 1302 ++++ po/nl.po | 1801 +++++ po/nn.po | 1920 +++++ po/oc.po | 1304 ++++ po/or.po | 1598 ++++ po/pa.po | 1950 +++++ po/pl.po | 1723 +++++ po/ps.po | 1241 ++++ po/pt.po | 2105 ++++++ po/pt_BR.po | 1969 +++++ po/ro.po | 1797 +++++ po/ru.po | 1676 +++++ po/rw.po | 841 +++ po/si.po | 1363 ++++ po/sk.po | 1323 ++++ po/sl.po | 1776 +++++ po/sq.po | 1322 ++++ po/sr.po | 2123 ++++++ po/sr@latin.po | 2123 ++++++ po/sv.po | 1755 +++++ po/ta.po | 1770 +++++ po/te.po | 1790 +++++ po/th.po | 2196 ++++++ po/tr.po | 1477 ++++ po/uk.po | 1546 ++++ po/vi.po | 1424 ++++ po/wa.po | 682 ++ po/zh_CN.po | 1853 +++++ po/zh_HK.po | 1926 +++++ po/zh_TW.po | 1941 +++++ previewer/Makefile.am | 37 + previewer/ev-previewer-window.c | 723 ++ previewer/ev-previewer-window.h | 51 + previewer/ev-previewer.c | 205 + properties/Makefile.am | 35 + properties/ev-properties-main.c | 159 + properties/ev-properties-view.c | 399 + properties/ev-properties-view.h | 50 + shell/Makefile.am | 160 + shell/eggfindbar.c | 740 ++ shell/eggfindbar.h | 82 + shell/ev-annotation-properties-dialog.c | 327 + shell/ev-annotation-properties-dialog.h | 54 + shell/ev-application.c | 1151 +++ shell/ev-application.h | 88 + shell/ev-convert-metadata.c | 302 + shell/ev-daemon.c | 553 ++ shell/ev-file-monitor.c | 168 + shell/ev-file-monitor.h | 57 + shell/ev-history.c | 148 + shell/ev-history.h | 65 + shell/ev-keyring.c | 116 + shell/ev-keyring.h | 37 + shell/ev-marshal.list | 1 + shell/ev-media-player-keys.c | 233 + shell/ev-media-player-keys.h | 49 + shell/ev-message-area.c | 354 + shell/ev-message-area.h | 74 + shell/ev-metadata.c | 313 + shell/ev-metadata.h | 69 + shell/ev-navigation-action-widget.c | 214 + shell/ev-navigation-action-widget.h | 46 + shell/ev-navigation-action.c | 247 + shell/ev-navigation-action.h | 64 + shell/ev-open-recent-action.c | 101 + shell/ev-open-recent-action.h | 52 + shell/ev-password-view.c | 434 ++ shell/ev-password-view.h | 59 + shell/ev-progress-message-area.c | 198 + shell/ev-progress-message-area.h | 64 + shell/ev-properties-dialog.c | 138 + shell/ev-properties-dialog.h | 47 + shell/ev-properties-fonts.c | 224 + shell/ev-properties-fonts.h | 48 + shell/ev-properties-license.c | 169 + shell/ev-properties-license.h | 50 + shell/ev-sidebar-annotations.c | 542 ++ shell/ev-sidebar-annotations.h | 62 + shell/ev-sidebar-attachments.c | 707 ++ shell/ev-sidebar-attachments.h | 62 + shell/ev-sidebar-layers.c | 411 + shell/ev-sidebar-layers.h | 58 + shell/ev-sidebar-links.c | 760 ++ shell/ev-sidebar-links.h | 66 + shell/ev-sidebar-page.c | 92 + shell/ev-sidebar-page.h | 64 + shell/ev-sidebar-thumbnails.c | 974 +++ shell/ev-sidebar-thumbnails.h | 59 + shell/ev-sidebar.c | 543 ++ shell/ev-sidebar.h | 67 + shell/ev-utils.c | 389 + shell/ev-utils.h | 47 + shell/ev-window-title.c | 203 + shell/ev-window-title.h | 47 + shell/ev-window.c | 7149 ++++++++++++++++++ shell/ev-window.h | 93 + shell/evince-icon.rc | 1 + shell/main.c | 328 + test/Makefile.am | 19 + test/test-encrypt.pdf | Bin 0 -> 26501 bytes test/test-links.pdf | Bin 0 -> 15151 bytes test/test-mime.bin | Bin 0 -> 15151 bytes test/test-page-labels.pdf | Bin 0 -> 10150 bytes test/test1.py | 36 + test/test2.py | 28 + test/test3.py | 15 + test/test4.py | 20 + test/test5.py | 24 + test/test6.py | 31 + test/test7.py | 33 + thumbnailer/Makefile.am | 83 + thumbnailer/evince-thumbnailer-comics.schemas.in | 106 + thumbnailer/evince-thumbnailer-djvu.schemas.in | 30 + thumbnailer/evince-thumbnailer-dvi.schemas.in | 83 + thumbnailer/evince-thumbnailer-ps.schemas.in | 154 + thumbnailer/evince-thumbnailer.c | 309 + thumbnailer/evince-thumbnailer.schemas.in | 80 + 602 files changed, 287728 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 MAINTAINERS create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 TODO create mode 100755 autogen.sh create mode 100644 backend/Makefile.am create mode 100644 backend/backend.symbols create mode 100644 backend/comics/Makefile.am create mode 100644 backend/comics/comics-document.c create mode 100644 backend/comics/comics-document.h create mode 100644 backend/comics/comicsdocument.evince-backend.in create mode 100644 backend/djvu/Makefile.am create mode 100644 backend/djvu/djvu-document-private.h create mode 100644 backend/djvu/djvu-document.c create mode 100644 backend/djvu/djvu-document.h create mode 100644 backend/djvu/djvu-links.c create mode 100644 backend/djvu/djvu-links.h create mode 100644 backend/djvu/djvu-text-page.c create mode 100644 backend/djvu/djvu-text-page.h create mode 100644 backend/djvu/djvudocument.evince-backend.in create mode 100644 backend/dvi/Makefile.am create mode 100644 backend/dvi/cairo-device.c create mode 100644 backend/dvi/cairo-device.h create mode 100644 backend/dvi/dvi-document.c create mode 100644 backend/dvi/dvi-document.h create mode 100644 backend/dvi/dvidocument.evince-backend.in create mode 100644 backend/dvi/fonts.c create mode 100644 backend/dvi/fonts.h create mode 100644 backend/dvi/mdvi-lib/Makefile.am create mode 100644 backend/dvi/mdvi-lib/afmparse.c create mode 100644 backend/dvi/mdvi-lib/afmparse.h create mode 100644 backend/dvi/mdvi-lib/bitmap.c create mode 100644 backend/dvi/mdvi-lib/bitmap.h create mode 100644 backend/dvi/mdvi-lib/color.c create mode 100644 backend/dvi/mdvi-lib/color.h create mode 100644 backend/dvi/mdvi-lib/common.c create mode 100644 backend/dvi/mdvi-lib/common.h create mode 100644 backend/dvi/mdvi-lib/defaults.h create mode 100644 backend/dvi/mdvi-lib/dviopcodes.h create mode 100644 backend/dvi/mdvi-lib/dviread.c create mode 100644 backend/dvi/mdvi-lib/files.c create mode 100644 backend/dvi/mdvi-lib/font.c create mode 100644 backend/dvi/mdvi-lib/fontmap.c create mode 100644 backend/dvi/mdvi-lib/fontmap.h create mode 100644 backend/dvi/mdvi-lib/fontsrch.c create mode 100644 backend/dvi/mdvi-lib/gf.c create mode 100644 backend/dvi/mdvi-lib/hash.c create mode 100644 backend/dvi/mdvi-lib/hash.h create mode 100644 backend/dvi/mdvi-lib/list.c create mode 100644 backend/dvi/mdvi-lib/mdvi.h create mode 100644 backend/dvi/mdvi-lib/pagesel.c create mode 100644 backend/dvi/mdvi-lib/paper.c create mode 100644 backend/dvi/mdvi-lib/paper.h create mode 100644 backend/dvi/mdvi-lib/pk.c create mode 100644 backend/dvi/mdvi-lib/private.h create mode 100644 backend/dvi/mdvi-lib/setup.c create mode 100644 backend/dvi/mdvi-lib/sp-epsf.c create mode 100644 backend/dvi/mdvi-lib/special.c create mode 100644 backend/dvi/mdvi-lib/sysdeps.h create mode 100644 backend/dvi/mdvi-lib/t1.c create mode 100644 backend/dvi/mdvi-lib/tfm.c create mode 100644 backend/dvi/mdvi-lib/tfmfile.c create mode 100644 backend/dvi/mdvi-lib/tt.c create mode 100644 backend/dvi/mdvi-lib/util.c create mode 100644 backend/dvi/mdvi-lib/vf.c create mode 100644 backend/dvi/texmfcnf.c create mode 100644 backend/dvi/texmfcnf.h create mode 100644 backend/impress/Makefile.am create mode 100644 backend/impress/common.h create mode 100644 backend/impress/document.c create mode 100644 backend/impress/f_oasis.c create mode 100644 backend/impress/f_oo13.c create mode 100644 backend/impress/iksemel.c create mode 100644 backend/impress/iksemel.h create mode 100644 backend/impress/imposter.h create mode 100644 backend/impress/impress-document.c create mode 100644 backend/impress/impress-document.h create mode 100644 backend/impress/impressdocument.evince-backend.in create mode 100644 backend/impress/internal.h create mode 100644 backend/impress/r_back.c create mode 100644 backend/impress/r_draw.c create mode 100644 backend/impress/r_geometry.c create mode 100644 backend/impress/r_gradient.c create mode 100644 backend/impress/r_style.c create mode 100644 backend/impress/r_text.c create mode 100644 backend/impress/render.c create mode 100644 backend/impress/zip.c create mode 100644 backend/impress/zip.h create mode 100644 backend/pdf/Makefile.am create mode 100644 backend/pdf/ev-poppler.cc create mode 100644 backend/pdf/ev-poppler.h create mode 100644 backend/pdf/pdfdocument.evince-backend.in create mode 100644 backend/pixbuf/Makefile.am create mode 100644 backend/pixbuf/pixbuf-document.c create mode 100644 backend/pixbuf/pixbuf-document.h create mode 100644 backend/pixbuf/pixbufdocument.evince-backend.in create mode 100644 backend/ps/Makefile.am create mode 100644 backend/ps/ev-spectre.c create mode 100644 backend/ps/ev-spectre.h create mode 100644 backend/ps/psdocument.evince-backend.in create mode 100644 backend/tiff/Makefile.am create mode 100644 backend/tiff/tiff-document.c create mode 100644 backend/tiff/tiff-document.h create mode 100644 backend/tiff/tiff2ps.c create mode 100644 backend/tiff/tiff2ps.h create mode 100644 backend/tiff/tiffdocument.evince-backend.in create mode 100644 config.h.in~ create mode 100644 configure.ac create mode 100644 cut-n-paste/Makefile.am create mode 100644 cut-n-paste/gimpcellrenderertoggle/Makefile.am create mode 100644 cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.c create mode 100644 cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.h create mode 100644 cut-n-paste/gimpcellrenderertoggle/gimpwidgetsmarshal.list create mode 100644 cut-n-paste/smclient/Makefile.am create mode 100644 cut-n-paste/smclient/eggdesktopfile.c create mode 100644 cut-n-paste/smclient/eggdesktopfile.h create mode 100644 cut-n-paste/smclient/eggsmclient-osx.c create mode 100644 cut-n-paste/smclient/eggsmclient-private.h create mode 100644 cut-n-paste/smclient/eggsmclient-win32.c create mode 100644 cut-n-paste/smclient/eggsmclient-xsmp.c create mode 100644 cut-n-paste/smclient/eggsmclient.c create mode 100644 cut-n-paste/smclient/eggsmclient.h create mode 100644 cut-n-paste/synctex/Makefile.am create mode 100644 cut-n-paste/synctex/synctex_parser.c create mode 100644 cut-n-paste/synctex/synctex_parser.h create mode 100644 cut-n-paste/synctex/synctex_parser_utils.c create mode 100644 cut-n-paste/synctex/synctex_parser_utils.h create mode 100644 cut-n-paste/toolbar-editor/Makefile.am create mode 100644 cut-n-paste/toolbar-editor/egg-editable-toolbar.c create mode 100644 cut-n-paste/toolbar-editor/egg-editable-toolbar.h create mode 100644 cut-n-paste/toolbar-editor/egg-toolbar-editor.c create mode 100644 cut-n-paste/toolbar-editor/egg-toolbar-editor.h create mode 100644 cut-n-paste/toolbar-editor/egg-toolbars-model.c create mode 100644 cut-n-paste/toolbar-editor/egg-toolbars-model.h create mode 100644 cut-n-paste/toolbar-editor/eggmarshalers.list create mode 100644 cut-n-paste/totem-screensaver/Makefile.am create mode 100644 cut-n-paste/totem-screensaver/README create mode 100644 cut-n-paste/totem-screensaver/totem-scrsaver.c create mode 100644 cut-n-paste/totem-screensaver/totem-scrsaver.h create mode 100644 cut-n-paste/zoom-control/Makefile.am create mode 100644 cut-n-paste/zoom-control/ephy-zoom-action.c create mode 100644 cut-n-paste/zoom-control/ephy-zoom-action.h create mode 100644 cut-n-paste/zoom-control/ephy-zoom-control.c create mode 100644 cut-n-paste/zoom-control/ephy-zoom-control.h create mode 100644 cut-n-paste/zoom-control/ephy-zoom.c create mode 100644 cut-n-paste/zoom-control/ephy-zoom.h create mode 100644 data/Makefile.am create mode 100644 data/evince-previewer-ui.xml create mode 100644 data/evince-toolbar.xml create mode 100644 data/evince-ui.xml create mode 100644 data/evince.1 create mode 100644 data/evince.convert create mode 100644 data/evince.desktop.in.in create mode 100644 data/evince.ico create mode 100644 data/hand-open.png create mode 100644 data/icons/16x16/Makefile.am create mode 100644 data/icons/16x16/actions/Makefile.am create mode 100644 data/icons/16x16/actions/object-rotate-left.png create mode 100644 data/icons/16x16/actions/object-rotate-right.png create mode 100644 data/icons/16x16/actions/view-page-continuous.png create mode 100644 data/icons/16x16/actions/view-page-continuous.xcf create mode 100644 data/icons/16x16/actions/view-page-facing.png create mode 100644 data/icons/16x16/actions/view-page-facing.xcf create mode 100644 data/icons/16x16/actions/zoom-fit-height.png create mode 100644 data/icons/16x16/actions/zoom-fit-page.svg create mode 100644 data/icons/16x16/actions/zoom-fit-width.png create mode 100644 data/icons/16x16/actions/zoom-fit-width.svg create mode 100644 data/icons/16x16/actions/zoom.png create mode 100644 data/icons/16x16/actions/zoom.svg create mode 100644 data/icons/16x16/apps/Makefile.am create mode 100644 data/icons/16x16/apps/evince.png create mode 100644 data/icons/16x16/mimetypes/Makefile.am create mode 100644 data/icons/16x16/mimetypes/x-office-presentation.png create mode 100644 data/icons/22x22/Makefile.am create mode 100644 data/icons/22x22/actions/Makefile.am create mode 100644 data/icons/22x22/actions/eye.png create mode 100644 data/icons/22x22/actions/object-rotate-left.png create mode 100644 data/icons/22x22/actions/object-rotate-right.png create mode 100644 data/icons/22x22/actions/view-page-continuous.png create mode 100644 data/icons/22x22/actions/view-page-continuous.xcf create mode 100644 data/icons/22x22/actions/view-page-facing.png create mode 100644 data/icons/22x22/actions/view-page-facing.xcf create mode 100644 data/icons/22x22/actions/zoom-fit-page.png create mode 100644 data/icons/22x22/actions/zoom-fit-page.svg create mode 100644 data/icons/22x22/actions/zoom-fit-width.png create mode 100644 data/icons/22x22/actions/zoom-fit-width.svg create mode 100644 data/icons/22x22/actions/zoom.png create mode 100644 data/icons/22x22/actions/zoom.svg create mode 100644 data/icons/22x22/apps/Makefile.am create mode 100644 data/icons/22x22/apps/evince.png create mode 100644 data/icons/22x22/mimetypes/Makefile.am create mode 100644 data/icons/22x22/mimetypes/x-office-presentation.png create mode 100644 data/icons/24x24/Makefile.am create mode 100644 data/icons/24x24/actions/Makefile.am create mode 100644 data/icons/24x24/actions/object-rotate-left.png create mode 100644 data/icons/24x24/actions/object-rotate-right.png create mode 100644 data/icons/24x24/actions/stock_filters-invert.png create mode 100644 data/icons/24x24/actions/view-page-continuous.png create mode 100644 data/icons/24x24/actions/view-page-facing.png create mode 100644 data/icons/24x24/actions/zoom-fit-height.png create mode 100644 data/icons/24x24/actions/zoom-fit-width.png create mode 100644 data/icons/24x24/actions/zoom.png create mode 100644 data/icons/24x24/apps/Makefile.am create mode 100644 data/icons/24x24/apps/evince.png create mode 100644 data/icons/24x24/mimetypes/Makefile.am create mode 100644 data/icons/24x24/mimetypes/x-office-presentation.png create mode 100644 data/icons/32x32/Makefile.am create mode 100644 data/icons/32x32/actions/Makefile.am create mode 100644 data/icons/32x32/actions/object-rotate-left.png create mode 100644 data/icons/32x32/actions/object-rotate-right.png create mode 100644 data/icons/32x32/actions/view-page-continuous.png create mode 100644 data/icons/32x32/actions/view-page-facing.png create mode 100644 data/icons/32x32/mimetypes/Makefile.am create mode 100644 data/icons/32x32/mimetypes/x-office-presentation.png create mode 100644 data/icons/48x48/Makefile.am create mode 100644 data/icons/48x48/actions/Makefile.am create mode 100644 data/icons/48x48/actions/close.png create mode 100644 data/icons/48x48/actions/resize-se.png create mode 100644 data/icons/48x48/actions/resize-sw.png create mode 100644 data/icons/48x48/actions/view-page-continuous.png create mode 100644 data/icons/48x48/actions/view-page-facing.png create mode 100644 data/icons/48x48/apps/Makefile.am create mode 100644 data/icons/48x48/apps/evince.png create mode 100644 data/icons/Makefile.am create mode 100644 data/icons/scalable/Makefile.am create mode 100644 data/icons/scalable/actions/Makefile.am create mode 100644 data/icons/scalable/actions/object-rotate-left.svg create mode 100644 data/icons/scalable/actions/object-rotate-right.svg create mode 100644 data/icons/scalable/apps/Makefile.am create mode 100644 data/icons/scalable/apps/evince.svg create mode 100644 data/icons/scalable/mimetypes/Makefile.am create mode 100644 data/icons/scalable/mimetypes/x-office-presentation.svg create mode 100644 data/org.mate.Evince.gschema.xml.in create mode 100644 data/org.mate.evince.Daemon.service.in create mode 100644 evince-document.h create mode 100644 evince-document.pc.in create mode 100644 evince-view.h create mode 100644 evince-view.pc.in create mode 100644 help/C/evince.xml create mode 100644 help/C/figures/evince_start_window.png create mode 100644 help/C/legal.xml create mode 100644 help/ChangeLog create mode 100644 help/Makefile.am create mode 100644 help/bg/bg.po create mode 100644 help/bg/figures/evince_start_window.png create mode 100644 help/ca/ca.po create mode 100644 help/ca/figures/evince_start_window.png create mode 100644 help/cs/cs.po create mode 100644 help/cs/figures/evince_start_window.png create mode 100644 help/de/de.po create mode 100644 help/de/figures/evince_start_window.png create mode 100644 help/el/el.po create mode 100644 help/el/figures/evince_start_window.png create mode 100644 help/en_GB/en_GB.po create mode 100644 help/es/es.po create mode 100644 help/es/figures/evince_start_window.png create mode 100644 help/eu/eu.po create mode 100644 help/eu/figures/evince_start_window.png create mode 100644 help/evince.omf.in create mode 100644 help/fi/fi.po create mode 100644 help/fi/figures/evince_start_window.png create mode 100644 help/fr/figures/evince_start_window.png create mode 100644 help/fr/fr.po create mode 100644 help/it/figures/evince_start_window.png create mode 100644 help/it/it.po create mode 100644 help/ja/figures/evince_start_window.png create mode 100644 help/ja/ja.po create mode 100644 help/nl/figures/evince_start_window.png create mode 100644 help/nl/nl.po create mode 100644 help/oc/oc.po create mode 100644 help/pt_BR/pt_BR.po create mode 100644 help/reference/Makefile.am create mode 100644 help/reference/libdocument/Makefile.am create mode 100644 help/reference/libdocument/libevdocument-docs.xml create mode 100644 help/reference/libdocument/libevdocument-overrides.txt create mode 100644 help/reference/libdocument/libevdocument-sections.txt create mode 100644 help/reference/libdocument/libevdocument.types create mode 100644 help/reference/libdocument/version.xml.in create mode 100644 help/reference/libview/Makefile.am create mode 100644 help/reference/libview/libevview-docs.xml create mode 100644 help/reference/libview/libevview-overrides.txt create mode 100644 help/reference/libview/libevview-sections.txt create mode 100644 help/reference/libview/libevview.types create mode 100644 help/reference/libview/version.xml.in create mode 100644 help/reference/shell/Makefile.am create mode 100644 help/reference/shell/evince-docs.xml create mode 100644 help/reference/shell/evince-overrides.txt create mode 100644 help/reference/shell/evince-sections.txt create mode 100644 help/reference/shell/evince.types create mode 100644 help/reference/shell/version.xml.in create mode 100644 help/ru/ru.po create mode 100644 help/sl/sl.po create mode 100644 help/sr/sr.po create mode 100644 help/sv/figures/evince_start_window.png create mode 100644 help/sv/sv.po create mode 100644 help/uk/figures/evince_start_window.png create mode 100644 help/uk/uk.po create mode 100644 help/vi/vi.po create mode 100644 help/zh_CN/zh_CN.po create mode 100644 libdocument/Makefile.am create mode 100644 libdocument/ev-annotation.c create mode 100644 libdocument/ev-annotation.h create mode 100644 libdocument/ev-async-renderer.c create mode 100644 libdocument/ev-async-renderer.h create mode 100644 libdocument/ev-attachment.c create mode 100644 libdocument/ev-attachment.h create mode 100644 libdocument/ev-backends-manager.c create mode 100644 libdocument/ev-backends-manager.h create mode 100644 libdocument/ev-debug.c create mode 100644 libdocument/ev-debug.h create mode 100644 libdocument/ev-document-annotations.c create mode 100644 libdocument/ev-document-annotations.h create mode 100644 libdocument/ev-document-attachments.c create mode 100644 libdocument/ev-document-attachments.h create mode 100644 libdocument/ev-document-factory.c create mode 100644 libdocument/ev-document-factory.h create mode 100644 libdocument/ev-document-find.c create mode 100644 libdocument/ev-document-find.h create mode 100644 libdocument/ev-document-fonts.c create mode 100644 libdocument/ev-document-fonts.h create mode 100644 libdocument/ev-document-forms.c create mode 100644 libdocument/ev-document-forms.h create mode 100644 libdocument/ev-document-images.c create mode 100644 libdocument/ev-document-images.h create mode 100644 libdocument/ev-document-info.h create mode 100644 libdocument/ev-document-layers.c create mode 100644 libdocument/ev-document-layers.h create mode 100644 libdocument/ev-document-links.c create mode 100644 libdocument/ev-document-links.h create mode 100644 libdocument/ev-document-misc.c create mode 100644 libdocument/ev-document-misc.h create mode 100644 libdocument/ev-document-print.c create mode 100644 libdocument/ev-document-print.h create mode 100644 libdocument/ev-document-security.c create mode 100644 libdocument/ev-document-security.h create mode 100644 libdocument/ev-document-text.c create mode 100644 libdocument/ev-document-text.h create mode 100644 libdocument/ev-document-thumbnails.c create mode 100644 libdocument/ev-document-thumbnails.h create mode 100644 libdocument/ev-document-transition.c create mode 100644 libdocument/ev-document-transition.h create mode 100644 libdocument/ev-document-type-builtins.c.template create mode 100644 libdocument/ev-document-type-builtins.h.template create mode 100644 libdocument/ev-document.c create mode 100644 libdocument/ev-document.h create mode 100644 libdocument/ev-file-exporter.c create mode 100644 libdocument/ev-file-exporter.h create mode 100644 libdocument/ev-file-helpers.c create mode 100644 libdocument/ev-file-helpers.h create mode 100644 libdocument/ev-form-field.c create mode 100644 libdocument/ev-form-field.h create mode 100644 libdocument/ev-image.c create mode 100644 libdocument/ev-image.h create mode 100644 libdocument/ev-init.c create mode 100644 libdocument/ev-init.h create mode 100644 libdocument/ev-layer.c create mode 100644 libdocument/ev-layer.h create mode 100644 libdocument/ev-link-action.c create mode 100644 libdocument/ev-link-action.h create mode 100644 libdocument/ev-link-dest.c create mode 100644 libdocument/ev-link-dest.h create mode 100644 libdocument/ev-link.c create mode 100644 libdocument/ev-link.h create mode 100644 libdocument/ev-mapping-list.c create mode 100644 libdocument/ev-mapping-list.h create mode 100644 libdocument/ev-module.c create mode 100644 libdocument/ev-module.h create mode 100644 libdocument/ev-page.c create mode 100644 libdocument/ev-page.h create mode 100644 libdocument/ev-render-context.c create mode 100644 libdocument/ev-render-context.h create mode 100644 libdocument/ev-selection.c create mode 100644 libdocument/ev-selection.h create mode 100644 libdocument/ev-transition-effect.c create mode 100644 libdocument/ev-transition-effect.h create mode 100644 libdocument/ev-version.h.in create mode 100644 libmisc/Makefile.am create mode 100644 libmisc/ev-page-action-widget.c create mode 100644 libmisc/ev-page-action-widget.h create mode 100644 libmisc/ev-page-action.c create mode 100644 libmisc/ev-page-action.h create mode 100644 libview/Makefile.am create mode 100644 libview/ev-annotation-window.c create mode 100644 libview/ev-annotation-window.h create mode 100644 libview/ev-document-model.c create mode 100644 libview/ev-document-model.h create mode 100644 libview/ev-job-scheduler.c create mode 100644 libview/ev-job-scheduler.h create mode 100644 libview/ev-jobs.c create mode 100644 libview/ev-jobs.h create mode 100644 libview/ev-loading-window.c create mode 100644 libview/ev-loading-window.h create mode 100644 libview/ev-page-cache.c create mode 100644 libview/ev-page-cache.h create mode 100644 libview/ev-pixbuf-cache.c create mode 100644 libview/ev-pixbuf-cache.h create mode 100644 libview/ev-print-operation.c create mode 100644 libview/ev-print-operation.h create mode 100644 libview/ev-stock-icons.c create mode 100644 libview/ev-stock-icons.h create mode 100644 libview/ev-timeline.c create mode 100644 libview/ev-timeline.h create mode 100644 libview/ev-transition-animation.c create mode 100644 libview/ev-transition-animation.h create mode 100644 libview/ev-view-accessible.c create mode 100644 libview/ev-view-accessible.h create mode 100644 libview/ev-view-cursor.c create mode 100644 libview/ev-view-cursor.h create mode 100644 libview/ev-view-marshal.list create mode 100644 libview/ev-view-presentation.c create mode 100644 libview/ev-view-presentation.h create mode 100644 libview/ev-view-private.h create mode 100644 libview/ev-view-type-builtins.c.template create mode 100644 libview/ev-view-type-builtins.h.template create mode 100644 libview/ev-view.c create mode 100644 libview/ev-view.h create mode 100644 m4/gtk-doc.m4 create mode 100644 m4/intltool.m4 create mode 100644 m4/libtool.m4 create mode 100644 m4/ltoptions.m4 create mode 100644 m4/ltsugar.m4 create mode 100644 m4/ltversion.m4 create mode 100644 m4/lt~obsolete.m4 create mode 100644 m4/mate-doc-utils.m4 create mode 100644 po/ChangeLog create mode 100644 po/LINGUAS create mode 100644 po/POTFILES.in create mode 100644 po/POTFILES.skip create mode 100644 po/af.po create mode 100644 po/ar.po create mode 100644 po/as.po create mode 100644 po/ast.po create mode 100644 po/be.po create mode 100644 po/be@latin.po create mode 100644 po/bg.po create mode 100644 po/bn.po create mode 100644 po/bn_IN.po create mode 100644 po/br.po create mode 100644 po/ca.po create mode 100644 po/ca@valencia.po create mode 100644 po/cs.po create mode 100644 po/cy.po create mode 100644 po/da.po create mode 100644 po/de.po create mode 100644 po/dz.po create mode 100644 po/el.po create mode 100644 po/en@shaw.po create mode 100644 po/en_CA.po create mode 100644 po/en_GB.po create mode 100644 po/eo.po create mode 100644 po/es.po create mode 100644 po/et.po create mode 100644 po/eu.po create mode 100644 po/fa.po create mode 100644 po/fi.po create mode 100644 po/fr.po create mode 100644 po/ga.po create mode 100644 po/gl.po create mode 100644 po/gu.po create mode 100644 po/he.po create mode 100644 po/hi.po create mode 100644 po/hu.po create mode 100644 po/id.po create mode 100644 po/it.po create mode 100644 po/ja.po create mode 100644 po/ka.po create mode 100644 po/kk.po create mode 100644 po/kn.po create mode 100644 po/ko.po create mode 100644 po/ks.po create mode 100644 po/ku.po create mode 100644 po/lt.po create mode 100644 po/lv.po create mode 100644 po/mai.po create mode 100644 po/mg.po create mode 100644 po/mk.po create mode 100644 po/ml.po create mode 100644 po/mn.po create mode 100644 po/mr.po create mode 100644 po/ms.po create mode 100644 po/nb.po create mode 100644 po/nds.po create mode 100644 po/ne.po create mode 100644 po/nl.po create mode 100644 po/nn.po create mode 100644 po/oc.po create mode 100644 po/or.po create mode 100644 po/pa.po create mode 100644 po/pl.po create mode 100644 po/ps.po create mode 100644 po/pt.po create mode 100644 po/pt_BR.po create mode 100644 po/ro.po create mode 100644 po/ru.po create mode 100644 po/rw.po create mode 100644 po/si.po create mode 100644 po/sk.po create mode 100644 po/sl.po create mode 100644 po/sq.po create mode 100644 po/sr.po create mode 100644 po/sr@latin.po create mode 100644 po/sv.po create mode 100644 po/ta.po create mode 100644 po/te.po create mode 100644 po/th.po create mode 100644 po/tr.po create mode 100644 po/uk.po create mode 100644 po/vi.po create mode 100644 po/wa.po create mode 100644 po/zh_CN.po create mode 100644 po/zh_HK.po create mode 100644 po/zh_TW.po create mode 100644 previewer/Makefile.am create mode 100644 previewer/ev-previewer-window.c create mode 100644 previewer/ev-previewer-window.h create mode 100644 previewer/ev-previewer.c create mode 100644 properties/Makefile.am create mode 100644 properties/ev-properties-main.c create mode 100644 properties/ev-properties-view.c create mode 100644 properties/ev-properties-view.h create mode 100644 shell/Makefile.am create mode 100644 shell/eggfindbar.c create mode 100644 shell/eggfindbar.h create mode 100644 shell/ev-annotation-properties-dialog.c create mode 100644 shell/ev-annotation-properties-dialog.h create mode 100644 shell/ev-application.c create mode 100644 shell/ev-application.h create mode 100644 shell/ev-convert-metadata.c create mode 100644 shell/ev-daemon.c create mode 100644 shell/ev-file-monitor.c create mode 100644 shell/ev-file-monitor.h create mode 100644 shell/ev-history.c create mode 100644 shell/ev-history.h create mode 100644 shell/ev-keyring.c create mode 100644 shell/ev-keyring.h create mode 100644 shell/ev-marshal.list create mode 100644 shell/ev-media-player-keys.c create mode 100644 shell/ev-media-player-keys.h create mode 100644 shell/ev-message-area.c create mode 100644 shell/ev-message-area.h create mode 100644 shell/ev-metadata.c create mode 100644 shell/ev-metadata.h create mode 100644 shell/ev-navigation-action-widget.c create mode 100644 shell/ev-navigation-action-widget.h create mode 100644 shell/ev-navigation-action.c create mode 100644 shell/ev-navigation-action.h create mode 100644 shell/ev-open-recent-action.c create mode 100644 shell/ev-open-recent-action.h create mode 100644 shell/ev-password-view.c create mode 100644 shell/ev-password-view.h create mode 100644 shell/ev-progress-message-area.c create mode 100644 shell/ev-progress-message-area.h create mode 100644 shell/ev-properties-dialog.c create mode 100644 shell/ev-properties-dialog.h create mode 100644 shell/ev-properties-fonts.c create mode 100644 shell/ev-properties-fonts.h create mode 100644 shell/ev-properties-license.c create mode 100644 shell/ev-properties-license.h create mode 100644 shell/ev-sidebar-annotations.c create mode 100644 shell/ev-sidebar-annotations.h create mode 100644 shell/ev-sidebar-attachments.c create mode 100644 shell/ev-sidebar-attachments.h create mode 100644 shell/ev-sidebar-layers.c create mode 100644 shell/ev-sidebar-layers.h create mode 100644 shell/ev-sidebar-links.c create mode 100644 shell/ev-sidebar-links.h create mode 100644 shell/ev-sidebar-page.c create mode 100644 shell/ev-sidebar-page.h create mode 100644 shell/ev-sidebar-thumbnails.c create mode 100644 shell/ev-sidebar-thumbnails.h create mode 100644 shell/ev-sidebar.c create mode 100644 shell/ev-sidebar.h create mode 100644 shell/ev-utils.c create mode 100644 shell/ev-utils.h create mode 100644 shell/ev-window-title.c create mode 100644 shell/ev-window-title.h create mode 100644 shell/ev-window.c create mode 100644 shell/ev-window.h create mode 100644 shell/evince-icon.rc create mode 100644 shell/main.c create mode 100644 test/Makefile.am create mode 100644 test/test-encrypt.pdf create mode 100644 test/test-links.pdf create mode 100644 test/test-mime.bin create mode 100644 test/test-page-labels.pdf create mode 100755 test/test1.py create mode 100755 test/test2.py create mode 100755 test/test3.py create mode 100755 test/test4.py create mode 100755 test/test5.py create mode 100755 test/test6.py create mode 100755 test/test7.py create mode 100644 thumbnailer/Makefile.am create mode 100644 thumbnailer/evince-thumbnailer-comics.schemas.in create mode 100644 thumbnailer/evince-thumbnailer-djvu.schemas.in create mode 100644 thumbnailer/evince-thumbnailer-dvi.schemas.in create mode 100644 thumbnailer/evince-thumbnailer-ps.schemas.in create mode 100644 thumbnailer/evince-thumbnailer.c create mode 100644 thumbnailer/evince-thumbnailer.schemas.in diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..64fbaefb --- /dev/null +++ b/AUTHORS @@ -0,0 +1,12 @@ +The Xpdf software and documentation are +Copyright 1996-2003 Glyph & Cog, LLC. + +Martin Kretzschmar +Marco Pesenti Gritti +Jonathan Blandford +Nickolay V. Shmyrev +Bryan Clark +Carlos Garcia Campos +Wouter Bolsterlee + +And many others diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..ecb192b2 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 00000000..3c7bfb8e --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,11 @@ +Jonathan Blandford +E-mail: jrb@gnome.org +Userid: jrb + +Nickolay V. Shmyrev +E-mail: nshmyrev@yandex.ru +Userid: nshmyrev + +Carlos Garcia Campos +E-mail: carlosgc@gnome.org +Userid: carlosgc diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..2468dae4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,116 @@ +ACLOCAL_AMFLAGS = -I m4 + +# Set the minimum required Windows version to Windows 2000 (0x0500). +if PLATFORM_WIN32 +AM_CPPFLAGS = -D_WIN32_WINNT=0x0500 +endif + +SUBDIRS = \ + cut-n-paste \ + data \ + libdocument \ + backend \ + libview \ + libmisc \ + properties \ + shell \ + po \ + help + +if ENABLE_TESTS +SUBDIRS += test +endif + +if ENABLE_THUMBNAILER +SUBDIRS += thumbnailer +endif + +if ENABLE_PREVIEWER +SUBDIRS += previewer +endif + +NULL = + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = \ + evince-document-$(EV_API_VERSION).pc \ + evince-view-$(EV_API_VERSION).pc \ + $(NULL) + +headerdir = $(includedir)/evince/$(EV_API_VERSION) +header_DATA = \ + evince-document.h \ + evince-view.h \ + $(NULL) + +# Applications + +intltool_extra = intltool-extract.in intltool-merge.in intltool-update.in + +EXTRA_DIST = \ + $(intltool_extra) \ + $(header_DATA) \ + autogen.sh \ + mate-doc-utils.make \ + MAINTAINERS + +DISTCLEANFILES = \ + mate-doc-utils.make \ + intltool-extract \ + intltool-merge \ + intltool-update + +MAINTAINERCLEANFILES = \ + ChangeLog \ + $(srcdir)/INSTALL \ + $(srcdir)/aclocal.m4 \ + $(srcdir)/autoscan.log \ + $(srcdir)/compile \ + $(srcdir)/config.guess \ + $(srcdir)/config.h.in \ + $(srcdir)/config.sub \ + $(srcdir)/configure \ + $(srcdir)/configure.scan \ + $(srcdir)/depcomp \ + $(srcdir)/install-sh \ + $(srcdir)/ltmain.sh \ + $(srcdir)/missing \ + $(srcdir)/mkinstalldirs \ + $(srcdir)/omf.make \ + $(srcdir)/xmldocs.make \ + $(srcdir)/gtk-doc.make \ + $(srcdir)/po/Makefile.in.in \ + `find "$(srcdir)" -type f -name Makefile.in -print` + +DISTCHECK_CONFIGURE_FLAGS = \ + --disable-schemas-install \ + --disable-scrollkeeper \ + --enable-gtk-doc \ + --disable-caja \ + --disable-tests \ + --disable-silent-rules + +# Ignore scrollkeeper issues for now. @#*$& scrollkeeper +distuninstallcheck_listfiles = find . -type f -print | grep -v scrollkeeper | grep -v /share/mate/help/ | grep -v \.omf + +distclean-local: + if test "$(srcdir)" = "."; then :; else \ + rm -f ChangeLog; \ + fi + +ChangeLog: + $(AM_V_GEN) if test -d "$(srcdir)/.git"; then \ + (GIT_DIR=$(top_srcdir)/.git ./missing --run git log -M -C --name-status --date=short --no-color) | fmt --split-only > $@.tmp \ + && mv -f $@.tmp $@ \ + || ($(RM) $@.tmp; \ + echo Failed to generate ChangeLog, your ChangeLog may be outdated >&2; \ + (test -f $@ || echo git log is required to generate this file >> $@)); \ + else \ + test -f $@ || \ + (echo A git checkout and git log is required to generate ChangeLog >&2 && \ + echo A git checkout and git log is required to generate this file >> $@); \ + fi + +.PHONY: ChangeLog + +-include $(top_srcdir)/git.mk diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..0af06a59 --- /dev/null +++ b/NEWS @@ -0,0 +1,2810 @@ +================ +Evince 2.32.0 +================ + +Bug fixes: + + * Make "Shrink to Printable Area" default option for Page Scaling + (Marek Kasik) + * Fix build with --disable-dbus (#629498, Didier Roche) + +Translation updates: + + * Damyan Ivanov (bg) + * Ivar Smolin (et) + * IƱaki LarraƱaga Murgoitio (eu) + * Milan Bouchet-Valat (fr) + * Francesco Marletta (it) + * Shushi Kurose (ja) + * Changwoo Ryu (ko) + * Wouter Bolsterlee (nl) + * Piotr Drąg (pl) + * Henrique P. Machado (pt_BR) + * Lucian Adrian Grijincu (ro) + * Yuri Myasoedov (ru) + +================ +Evince 2.31.92 +================ + +Bug fixes: + + * Change gsettings schema path from /apps/evince to + /org/mate/evince (Christian Persch) + * A D-Bus signal is now emitted when document is loaded (#626561, + JosĆ© Aliste) + * Use MateConf again for lockdown settings (#628945, Christian Persch) + * Fix a crash during selection when evince is built with gtk2 + (Carlos Garcia Campos) + * Evince daemon can spawn an evince instance when finding a + document (#625971, JosĆ© Aliste) + +Translation updates: + + * Khaled Hosny (ar) + * Kenneth Nielsen (da) + * Christian Kirbach (de) + * Michael Kotsarinis (el) + * Bruce Cowan (en_GB) + * Ivar Smolin (et) + * Gabor Kelemen (hu) + * Andika Triwidada (id) + * Shushi Kurose (ja) + * Baurzhan Muftakhidinov (kk) + * Aurimas Černius (lt) + * Kjartan Maraas (nb) + * Duarte Loreto (pt) + * MiloÅ” Popović (sr@latin) + * Милош ŠŸŠ¾ŠæŠ¾Š²ŠøŃ› (sr) + * Daniel Nylander (sv) + * ęœ±ę¶› (zh_CN) + * Chao-Hsiung Liao (zh_HK) + * Chao-Hsiung Liao (zh_TW) + +================ +Evince 2.31.90 +================ + +Bug fixes: + + * Do not jump to link dest when clicking with GDK_CONTROL_MASK + (#626407, Carlos Garcia Campos) + * Use GDK_BLANK_CURSOR instead of creating our own empty cursor + (Carlos Garcia Campos) + * Improve findbar message when there are no matches (#626334, + Carlos Garcia Campos, JosĆ© Aliste) + * Remove #ifdefs for old cairo versions (#626147, Juanjo MarĆ­n) + +Translation updates: + + * Paul Seyfert (de) + * Jorge GonzĆ”lez (es) + * Fran Dieguez (gl) + * Yaron Shahrabani (he) + * Torstein Adolf Winterseth (nn) + * A S Alam (pa) + * Matej Urbančič (sl) + * Xhacker Liu (zh_CN) + +================ +Evince 2.31.6.1 +================ + +New Features and UI Improvements: + + * Add --with-gtk=2.0|3.0 configure flag (#626030, Vincent Untz) + +Bug fixes: + + * Fix a crash with DVI files and empty glyphs (#600552, Marek Kasik) + * Fix a crash when a page is manually entered (#624936, Marek Kasik) + +Translation updates: + + * Jorge GonzĆ”lez (es) + * Fran Dieguez (gl) + * Yaron Shahrabani (he) + * Andika Triwidada (id) + * Kjartan Maraas (nb) + * Matej Urbančič (sl) + * Dr.T.Vasudevan (ta) + +================ +Evince 2.31.6 +================ + +New Features and UI Improvements: + + * Preliminary support for adding new annotations (#168304, Carlos + Garcia Campos) + * Add confirmation dialog on closing window when document has been + modified (Form fields have been filled out or annots have been + added/modified) (#168304, Carlos Garcia Campos) + * Add an action to edit menu to save current settings as + default. Default settings will be used only for new documents + that don't have metadata (#620325, Carlos Garcia Campos) + * Migrate lockdown to gsettings (Carlos Garcia Campos) + +Bug fixes: + + * Update synctex parser from TexLive SVN repository (#624532, + Marek Kasik) + * Fix a crash when loading document with inverted colors enabled + (Carlos Garcia Campos) + * Several build improvements (Hib Eris) + * Fix memory leaks when document is reloaded (Carlos Garcia Campos) + * Fix position of window annotations when moved by the user (Jose + Aliste, Carlos Garcia Campos) + * Fix build with poppler 0.14 (#624904, Carlos Garcia Campos) + * Use cairo instead of gdk_draw API (Carlos Garcia Campos) + * Only set text and text_layout flags in page cache when a11y + might be enabled (Carlos Garcia Campos) + +Translation updates: + + * Xandru Armesto (ast) + * Mario BlƤttermann (de) + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * Fran DiĆ©guez (gl) + * Sweta Kothari (gu) + * Yaron Shahrabani (he) + * Shankar Prasad (kn) + * Sandeep Shedmake (mr) + * Kjartan Maraas (nb) + * Daniel Nylander (sv) + * Krishna Babu K (te) + * Eleanor Chen (zh_CN) + * Chao-Hsiung Liao (zh_HK) + * Chao-Hsiung Liao (zh_TW) + +================ +Evince 2.31.5 +================ + +New Features and UI Improvements: + + * Add a new sidebar page to show the list of annotations (Carlos + Garcia Campos) + * Finish DBus API for synctex support (#543503, Jose Aliste) + * Switch to GTK+ 3 + +Bug fixes: + + * Make sure annot popups never show up outside the main window + * (#604125, Carlos Garcia Campos) + * Set word wrapping mode for text in popup annotations (#623456, + gvlat@pochta.ru) + +Translation updates: + + * Baurzhan Muftakhidinov (kk) + * ęœ±ę¶› (zh_CN) + +================ +Evince 2.31.4.1 +================ + +Bug fixes: + + * Fix build when poppler_page_get_text_layout is not available + (#623080, Carlos Garcia Campos) + +================ +Evince 2.31.4 +================ + +New Features and UI Improvements: + + * Implement AtkText interface in EvView (#309015, Daniel Garcia) + * Add preliminary SyncTex support (#543503, Jose Aliste, Carlos + Garcia Campos) + +Bug fixes: + + * Don't save print document settings into global file (#530018, + Carlos Garcia Campos) + * Reload presentation view when document changes (#621750, Carlos + Garcia Campos) + * Remove hard-coded reason in totem-scrsaver (#621386, Bastien + Nocera) + * Fix opening cbz files with [] in archive (#619248, Juanjo MarĆ­n) + +Translation updates: + + * Carles Ferrando Garcia (ca) + * Kristjan SCHMIDT (eo) + * Fran DiĆ©guez (gl) + * Kjartan Maraas (nb) + * Matej Urbančič (sl) + * ęØē«  (zh_CN) + +================ +Evince 2.31.3 +================ + +New Features and UI Improvements: + + * Use a dynamic pixbuf cache size based on document page + size. It allows us caching more pages for lower scale factors + and increase zoom level by caching fewer pages. (#303365, Carlos + Garcia Campos) + +Bug fixes: + + * Fix fade animations (#619825, Carlos Garcia Campos) + * Fix a crash when starting animation in presentation mode + (#619948, Carlos Garcia Campos) + * Remove set_focus_on_map and set NOTIFICATION type hint for + loading window. It prevents focus stealing and skips taskbar and + pager hints (#620548, Carlos Garcia Campos) + * Fix build with GSEAL enabled (#605776, Carlos Garcia Campos) + * Remove circular dependencies in Makefiles and fix some compiler + warnings (Hib Eris) + +Translation updates: + + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * Fran DiĆ©guez (gl) + * Yaron Shahrabani (he) + * Kjartan Maraas (nb) + * Sira Nokyoongtong (th) + * ē”˜éœ²(Gan Lu) (zh_CN) + +================ +Evince 2.31.2 +================ + +New Features and UI Improvements: + + * Port to GDBus (Christian Persch, Carlos Garcia Campos) + * Do not use the mateconf gsettings backend any more (#619335, + Matthias Clasen) + * Use cairo to draw search results (Carlos Garcia Campos) + * Use a loading window to show loading progress information + instead of the old 'Loading ...' text rendered on every page + (Carlos Garcia Campos) + +Bug fixes: + + * Fix localization on Windows (#610548, Hib Eris) + * Convert relative path to absolute path for evince-previewer + (Marek Kasik) + * Fix opening files with '#' in its name (#616515, Carlos Garcia + Campos) + * Accept keyboard focus in window annotations (Carlos Garcia + Campos) + * Fix most of the GSEAL build issues (#605776, Carlos Garcia + Campos) + * Add .c files to the introspection sources (#617736, Christian + Persch) + +Translation updates: + + * Thomas Thurman (en@shaw) + * Kjartan Maraas (nb) + * Wouter Bolsterlee (nl) + * Manoj Kumar Giri (or) + +================ +Evince 2.31.1 +================ + +New Features and UI Improvements: + + * Add saved documents with "save a copy" to recent file list + (#617580, Carlos Garcia Campos) + * Port override_restrictions mateconf key to GSettings (Carlos Garcia + Campos) + * Add icons to the recent items, and always show them in menu + (#614400, Christian Persch) + +Bug fixes: + + * Invert colors of pages and thumbanils when loading in inverted + color mode (#616110, #616111, Juanjo MarĆ­n) + * Take default settings from last document opened. Fixes + regression caused by migration to gio metadata (#606090, + Carlos Garcia Campos) + * Ellipsise the recent action's label (Christian Persch) + * Make inverted colors mode work in presentation mode too + (#614693, Carlos Garcia Campos) + * Respect MATE22_USER_DIR env variable (#613637, Ray Strode) + * Fix loading of local documents when uri contains a page + destination (#616515, Carlos Garcia Campos) + * Make mate-doc-utils optional (Hib Eris) + * Update icons to match mate-icon-theme appearance (#614747, + Hylke Bons, Carlos Garcia Campos) + * Make sure there's a new valid page range before updating caches + (fdo#27599, Carlos Garcia Campos) + * Fix .desktop file according to CorrectDesktopFiles GNIOME Goal + (#592733, Javier Jardón) + * Update FSF address everywhere (#514607, Arun Persaud) + * Fix loading of compressed password-protected documents (#613959, + Carlos Garcia Campos) + * Add command line option to open document at a given page index + (#613449, Daniel M. German) + * Fix build with GSEAL_ENABLE for libview and libdocument + (#605776, Hib Eris, Carlos Garcia Campos) + * Close previewer window with control + w (#612972, Carlos Garcia + Campos) + * Fix keybindings in previewer window (#612972, Carlos Garcia + Campos) + +Translation updates: + + * Runa Bhattacharjee (bn_IN) + * Gil Forcada (ca) + * Gil Forcada (ca@valencia) + * Jorge GonzĆ”lez (es) + * Fran DiĆ©guez (gl) + * Shankar Prasad (kn) + * Ani Peter (ml) + * Sandeep Shedmake (mr) + * Wouter Bolsterlee (nl) + * Andrej ŽnidarÅ”ič (sl) + +================ +Evince 2.30.0 +================ + +Bug fixes: + + * Fix scale calculation on rotated monitors in presentation mode + (JosĆ© Aliste, Carlos Garcia Campos) + * Save current page when closing window in presentation mode + (#612656, Carlos Garcia Campos) + * Fix rendering of documents with non uniform page size when in + presentation mode (Carlos Garcia Campos) + * Use monitor height instead of screen height to calculate page + scale in presentation mode (#608924, Carlos Garcia Campos) + +Translation updates: + + * Sadia Afroz (bn) + * Lucas Lommer (cs) + * Kenneth Nielsen (da) + * IƱaki LarraƱaga Murgoitio (eu) + * Tommi Vainikainen (fi) + * Gabor Kelemen (hu) + * Andika Triwidada (id) + * Francesco Marletta (it) + * Changwoo Ryu (ko) + * Badral (mn) + * Nils-Christoph Fiedler (nds) + * Wouter Bolsterlee (nl) + * A S Alam (pa) + * Duarte Loreto (pt) + * Daniel Nylander (sv) + * Maxim Dziumanenko (uk) + +================ +Evince 2.29.92 +================ + +Bug fixes: + + * Fix build with gtk+ >= 2.19.7 + +Translation updates: + + * Gil Forcada (ca) + * Bruce Cowan (en_GB) + * IƱaki LarraƱaga Murgoitio (eu) + * Gabor Kelemen (hu) + * Gintautas Miliauskas (lt) + * Anita Reitere (lv) + * Umarzuki bin Mochlis Moktar (ms) + * Piotr Drąg (pl) + * Yuri Kozlov (ru) + * MiloÅ” Popović (sr@latin) + * Милош ŠŸŠ¾ŠæŠ¾Š²ŠøŃ› (sr) + * Daniel Nylander (sv) + +================ +Evince 2.29.91 +================ + +New Features and UI Improvements: + + * DVI backend support on windows (#602910, Hib Eris) + +Documentation fixes: + + * Modernise API docs (Christian Persch) + +Bug fixes: + + * Fix dpi calculation (#608586, Juanjo MarĆ­n) + * Allow adding actions for more than one item in view popup menu + (#609497, Carlos Garcia Campos) + * Add mateconf schemas for comic cbt files (#609501, Carlos Garcia + Campos) + * Update totem-scrsaver from totem sources (#610173, Carlos Garcia + Campos) + * Fix linking with pedantic linkers (#609895, Hib Eris) + * Do not link evince shell to poppler directly (#608832, Hib Eris) + * Several translatable messages fixed (#608698, Philip Withnall) + +Translation updates: + + * Khaled Hosny (ar) + * Krasimir \"Bfaf\" Chonov (bg) + * Sadia Afroz (bn) + * Christian Kirbach (de) + * nikosCharonitakis (el) + * Philip Withnall (en_GB) + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * Claude Paroz (fr) + * Fran DiĆ©guez (gl) + * Hideki Yamane (Debian-JP) (ja) + * Kjartan Maraas (nb) + * Wouter Bolsterlee (nl) + * Henrique P. Machado (pt_BR) + * Adi Roiban (ro) + * Matej Urbančič (sl) + * Dr,T,Vasudevan (ta) + * Theppitak Karoonboonyanan (th) + * Chao-Hsiung Liao (zh_HK) + * Chao-Hsiung Liao (zh_TW) + +================ +Evince 2.29.5 +================ + +New Features and UI Improvements: + + * Make Home/End keys go to first/last page in presentation mode + too (#358462, Carlos Garcia Campos) + * Allow finish presentation by clicking on end page (#309364, + Carlos Garcia Campos) + * Add custom print tab to printing dialog with several page scale + options (#599468, #599470, #599547, Adrian Johnson) + * Add support for remote files in evince thumbnailers (#605218, + Carlos Garcia Campos) + * Add evince icon to the windows executable (#596401, Hib Eris) + +Bug fixes: + + * Fix transition of pages with duration=0 in presentation mode + (Carlos Garcia Campos) + * Fix vertical white line shown in presentation mode with + documents with black background (#438760, Carlos Garcia Campos) + * Fix fickering and blank screen issues when changing pages fast + in presentation mode (#602738, Carlos Garcia Campos) + * Fix printing on Windows (#604705, Hib Eris) + * Disable text selection in presentation mode (#605554, Carlos + Garcia Campos) + * Make comics backend also compile on Windows (#605146, Hib Eris) + * Remove comics_regex_quote() in favor of always using + g_shell_quote() (#605092, Juanjo MarĆ­n) + * Properly redrawn properties view when document is reloaded + (#605169, Carlos Garcia Campos) + +Translation updates: + + * Khaled Hosny (ar) + * Jorge GonzĆ”lez (es) + * Kjartan Maraas (nb) + * Matej Urbančič (sl) + * Daniel Nylander (sv) + * Theppitak Karoonboonyanan (th) + * Maxim Dziumanenko (uk) + +================ +Evince 2.29.4 +================ + +New Features and UI Improvements: + + * Always show rotate icons (#599398, Carlos Garcia Campos) + * EvPrintOperation has been moved to libview so that it can + be used by EvView users (#604750, Tomeu Vizoso) + * Support for cbt comic files (#588266, Juanjo MarĆ­n) + +Bug fixes: + + * Do not fail to open external uri links that are relative paths + (#604716, Carlos Garcia Campos) + * Do not hardcode BINDIR on Windows (#605001, Hib Eris) + * Fix a crash in djvu backend on windows (#604919, Hib Eris) + * Replace mkdtemp with our own _ev_g_mkdtemp to make it portable + (#604917, Hib Eris) + * Include a copy of some icons from mate-icon-theme to be used as + fallback on platforms like window where mate-icon-theme is not + available (#596400, Hib Eris) + * Fix 0x0 page size always shown in properties view (Carlos Garcia + Campos) + * Do not hide page selector widget on invalid input (#603714, + Carlos Garcia Campos) + * Split EvPixbufCache into dynamic and static data. Static page + data (links, annots, images, text and forms) that don't + depend on current scale/rotation, have been moved to EvPageCache + and are never removed. (#602405, Carlos Garcia Campos) + * Fix a typo in EV_RENDER_CONTEXT macro definition (#603857, + Gustavo Carneiro) + * Code cleanups and improvements in libview (#603858, Carlos + Garcia Campos) + +Translation updates: + + * Khaled Hosny (ar) + * Yaron Shahrabani (he) + * Kjartan Maraas (nb) + * Matej Urbančič (sl) + * Tao Wei (zh_CN) + +================ +Evince 2.29.3 +================ + +New Features and UI Improvements: + + * Add inverted colors view mode (#321823, Juanjo MarĆ­n, Carlos + Garcia Campos) + * Add options to open/save attachment annotations to context menu + (Carlos Garcia Campos) + +Bug fixes: + + * Fix infinite loop when scrollbar visibility changes in fit-with + mode (Carlos Garcia Campos) + * Fix rendering issue in dual/continuous mode (#602742, JosĆ© + Aliste) + * Do not resize the window when zoom in/out with CTRL+Scroll + (#602798, Carlos Garcia Campos) + * Keep scroll position when reloading (#602445, Carlos Garcia + Campos) + * Make "p" and "n" accels unsensitive while searching (Carlos + Garcia Campos) + +Translation updates: + + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * Yaron Shahrabani (he) + * Nils-Christoph Fiedler (nds) + * Matej Urbančič (sl) + * Tao Wei (zh_CN) + +================ +Evince 2.29.2 +================ + +New Features and UI Improvements: + + * Add support for PDF File Attachment Annotations (#601839, Carlos + Garcia Campos) + * Use "n" and "p" keys for page jump (#601217, Carlos Garcia + Campos) + * Update actions sensitivity when mateconf keys change (#568433, + Carlos Garcia Campos) + +Documentation fixes: + + * Make series ID unique (#599726, Christian Persch) + +Bug fixes: + + * Fix saving attachments (Carlos Garcia Campos) + * Make sure view is redrawn as soon as current page is rendered + (#601433, Carlos Garcia Campos) + * Fix documentation of default flag values in configure (Emmanuel + Fleury) + * Do not mark properties name and desc as translatable (#509824, + Carlos Garcia Campos) + * Fix build with glib < 2.22.0 (#600338, Christian Spurk) + * Disable print page setup options according to lockdown (#568433, + Carlos Garcia Campos) + * Fix loading of remote files (Carlos Garcia Campos) + * Correctly process --with-smclient arguments (#590174, Christian + Persch) + +Translation updates: + + * Thomas Thurman (en@shaw) + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * Antón MĆ©ixome (gl) + * Kjartan Maraas (nb) + * Matej Urbančič (sl) + * Daniel Nylander (sv) + * Dr.T.Vasudevan (ta) + * ęØŠę –ę±Ÿ(Fan Qijiang) (zh_CN) + +================ +Evince 2.29.1 +================ + +New Features and UI Improvements: + + * Embed page setup settings in the print dialog (#591551, Marek + KaŔík, Carlos Garcia Campos) + * A new tab has been added to properties dialog to show + * information about the document license (#349173, Juanjo MarĆ­n, + Carlos Garcia Campos) + * Use different processes for every document opened. (#583680, + #434966, #497388, #524633, #586087, Carlos Garcia Campos) + * Use GVFS metadata instead of our own xml based + implementation. (#586841, Carlos Garcia Campos) + * When scrolling a page, move one screen leaving one line context + to help to visually track the document as it moves. (#170871, + Carlos Garcia Campos) + * Use GtkInfoBar instead of GeditMessageArea for the message area + (#592785, Carlos Garcia Campos) + * EvPageCache has been splitted up into document part (moved to + EvDocument which has been converted into an abstract class + instead of an interface) and view part: EvDocumentModel. This + allows to have more than one EvView for the same document, so + that "Open a Copy" can be implemented without using the symlink + hack. (#573641, Carlos Garcia Campos) + +Bug fixes: + + * Several fixes and cleanups in comics backend (Christian Persch) + * Don't exit with non-writable $HOME (Christian Persch) + * Clean up temp file handling to make sure we can cope with not + being able to create our temp directory (#595372, Christian Persch) + * Set page orientation of each page when printing so that + documents with mixed portrait/landscape pages print correctly + (#599470, Adrian Johnson) + * Preserve aspect ratio when scaling page for printing (#599468, + Adrian Johnson) + * Fix a crash when we don't have a DBUS connection (Christian Persch) + * Make sure total_num_bytes > 0 before using it in progress + callbacks (#597691, Carlos Garcia Campos) + * Escape document URI before showing it in progress area (#597691, + Carlos Garcia Campos) + * Fix a crash due to an uninitialized variable (#597154, Carlos + Garcia Campos) + * Fix a crash when there are form field widgets active during + rotation (#593316, Carlos Garcia Campos) + * Remarks the selected thumbnail after rotation (#595548, Juanjo MarĆ­n) + * Don't change current page when rotating documents with different + page sizes (#595704, Carlos Garcia Campos) + * Fix thumbnails rotation when loading a rotated document + (#595718, Carlos Garcia Campos) + * Save images as png or jpg when the filename has no extensions + (#595079, Patrick Ammann) + * Populate destination page when reloading the document (#570054, + Fernando Herrera) + +Translation updates: + + * Khaled Hosny (ar) + * Astur (ast) + * Krasimir \"Bfaf\" Chonov (bg) + * Runa Bhattacharjee (bn_IN) + * Gil Forcada (ca) + * Marek Černocký (cs) + * Kenneth Nielsen (da) + * Mario BlƤttermann (de) + * Kostas Papadimas (el) + * Philip Withnall (en_GB) + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * IƱaki LarraƱaga Murgoitio (eu) + * Tommi Vainikainen (fi) + * Claude Paroz (fr) + * Antón MĆ©ixome (gl) + * Sweta Kothari (gu) + * Gabor Kelemen (hu) + * Andika Triwidada (id) + * Francesco Marletta (it) + * Shushi Kurose (ja) + * Shankar Prasad (kn) + * Changwoo Ryu (ko) + * Gintautas Miliauskas (lt) + * Sangeeta Kumari (mai) + * Ani Peter (ml) + * Sandeep Shedmake (mr) + * Kjartan Maraas (nb) + * Reinout van Schouwen (nl) + * Manoj Kumar Giri (or) + * A S Alam (pa) + * Piotr Drąg (pl) + * AndrĆ© Gondim (pt_BR) + * Duarte Loreto (pt) + * Lucian Adrian Grijincu (ro) + * Matej Urbančič (sl) + * MiloÅ” Popović (sr@latin) + * Милош ŠŸŠ¾ŠæŠ¾Š²ŠøŃ› (sr) + * Daniel Nylander (sv) + * Dr.T.Vasudevan (ta) + * Krishna Babu K (te) + * Theppitak Karoonboonyanan (th) + * Baris Cicek (tr) + * Maxim Dziumanenko (uk) + * Aron Xu (zh_CN) + * Chao-Hsiung Liao (zh_HK) + * Chao-Hsiung Liao (zh_TW) + +================ +Evince 2.27.90 +================ + +Bug fixes: + + * Let scroll wheel change page when in non-continuous best-fit + mode (#562257, Philip Langdale) + * Show print progress information when using GtkPrintOperation + * Don't show crash recovery dialog when requested file is the only + one to be recovered (#578894, Carlos Garcia Campos) + * Disable crash recovery when D-BUS is disabled (#578894, Carlos + Garcia Campos) + * Grab focus on view widget after setting metadata (#589300, + Nickolay V. Shmyrev) + * Fixes segmentation violation when print file format is empty + (#589226, Andreas Liebe) + * Use AM_V_GEN to make custom commands silent (#585355, Javier Jardón) + * Don't install schema files when building without mateconf (Nickolay + V. Shmyrev) + +Translation updates: + + * Khaled Hosny (ar) + * Maruf Ovee (bn) + * Denis (br) + * Gil Forcada (ca) + * Carles Ferrando Garcia (ca@valencia) + * Marek Černocký (cs) + * Mario BlƤttermann (de) + * SeĆ”n de BĆŗrca (ga) + * Antón MĆ©ixome (gl) + * Kjartan Maraas (nb) + * AndrĆ© Gondim (pt_BR) + * Daniel Nylander (sv) + * Theppitak Karoonboonyanan (th) + * Chao-Hsiung Liao (zh_HK) + * Chao-Hsiung Liao (zh_TW) + +================ +Evince 2.27.4 +================ + +Bug fixes: + + * Compute selections on button press for word/line selections + (#562059, Carlos Garcia Campos) + * Fix several even/odd multipage issues (#583429, Bartek Kostrzewa) + * Register thumbnailing for gzip/gzip dvi files (#588013, Emilio + Pozuelo Monfort) + * Get rid of shave with AM_SILENT_RULES automake option (#585355, + Javier Jardón) + * Make evince output pdf on supported printers (#585442, Johan Brannlund) + * Fix build on windows (Fridrich Strba) + * Determine data directory on runtime on windows (Fridrich Strba) + * Fix build without MateConf (Nickolay V. Shmyrev) + * Fix several introspection build issues (#585971, Christian Persch) + +Translation updates: + + * Giannis Katsampirhs (el) + * Jonathan Ernst (fr) + * Daniel Nylander (sv) + * Maxim Dziumanenko (uk) + * Clytie Siddall (vi) + * Nguyį»…n ĐƬnh Trung (vi) + +================ +Evince 2.27.3 +================ + +New Features and UI Improvements: + + * Update the hildon port (Christian Persch) + * Complete the win32 port (#339172, Hib Eris) + * Allow activate the menubar in fullscreen mode (#504243, Carlos + Garcia Campos) + * Do not create popup window if the annot doesn't have a popup + (Carlos Garcia Campos) + * Add tests for printer settings (#583976, Bartek Kostrzewa) + * Remember and reuse "Save a Copy..." path (#485195, Carlos Garcia Campos) + * Support for cb7 using the p7zip commands in comics backend + (#565174, Juanjo MarĆ­n) + * Parse xml metadata to detect PDF/A documents (#582206, Davide Capodaglio) + +Bug fixes: + + * Several build fixes and improvements (Christian Persch) + * Fix page transitions in presentation mode (#583652, #581881, + Carlos Garcia Campos) + * Scale according to paper size before rendering for printing + (Carlos Garcia Campos) + * Fix multipage even/odd printing issues (#583429, #583388, Bartek + Kostrzewa) + * Gracefully work with FITB destinations and friends (#583276, + Matthias Drochner) + * Fix double to int conversion in thumbnails_get_dimensions + (#581524, Carlos Garcia Campos) + * Ohter bugs fixed: #584617, #585497 + +Translation updates: + + * Runa Bhattacharjee (bn_IN) + * Mario BlƤttermann (de) + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * Kjartan Maraas (nb) + * Vladimir Melo (pt_BR) + * Dr.T.Vaasudevan (ta) + +================ +Evince 2.27.1 +================ + +New Features and UI Improvements: + + * Use GtkPrintOperation when printing for the backends that + support rendering into a cairo context (#557112, Carlos Garcia Campos) + * Recover previous session when running evince after a crash + (#578894, Carlos Garcia Campos) + * Preliminary annotations support (#315002, Carlos Garcia Campos, + IƱigo MartĆ­nez) + * Rename Print Setup menu entry as Page Setup for consistency with + the GTK+ dialog title (#581109, Nickolay V. Shmyrev) + * Added F3 as a find-next accelerator key (#579072, Michael J. Chudobiak) + * Support the free Gna! unrar tool in comics backend (#552074, + Juanjo MarĆ­n) + * Add evince-previewer as a separate applicaton that implements + the printing preview (Carlos Garcia Campos) + +Bug fixes: + + * Fix handling of the tmp folder (#582108, Juanjo MarĆ­n) + * Abort dnd operations originated in the same Evince window + (#582077, Carlos Garcia Campos) + * Disable bouncing during scrolling (#533534, Martin Pohlack) + * Fix build without mateconftool-2 (#581441, Christian Persch) + * Fix documentation build (#579671, Nickolay V. Shmyrev) + * Fix error handling of broken documents (#580886, Nickolay V. Shmyrev) + * Fix several memory leaks in comics backend (#552074, Juanjo MarĆ­n) + * Escape URIs for display (#581064, Nickolay V. Shmyrev) + * Resync with libegg to remove deprecated GTK+ symbols (#580556, + Javier Jardón) + * Correct check for exit status of commands in comics backend + (#579656, Juanjo MarĆ­n) + * Add -no-undefined flag for Cygwin build (#580058, Yaakov Selkowitz) + * Use g_file_make_symbolic_link to create symlinks (#339172, Hib Eris) + * Delete the temp symlink created when opening a copy (Carlos + Garcia Campos) + * Don't redraw again when zoom is set more than once to the same + scale factor (Carlos Garcia Campos) + * Fix print preview of empty selection (#517735, Juanjo MarĆ­n) + * Don't prevent unmounting in case the initial cwd is on an + external device (#575436, Carlos Garcia Campos) + * Create and load the document based on the mime-type provided by + caja instead of using our own documents factory. (#533917, + Carlos Garcia Campos) + * Fix endianess issues in dvi and tiff backends (#578433, #509920, + Benjamin Berg) + * Fix memory leak in tiff backend (#578285, Nickolay V. Shmyrev) + * Fix path where accels file is saved (#577500, Nickolay + V. Shmyrev) + * Fix fading animations (Nickolay V. Shmyrev) + * Translate the categories in the caja properties tab + (#575085, Christian Persch) + * Other bugs fixed: #539972, #578436, #582543 + +Translation updates: + + * Jorge Gonzalez (es) + * Manoj Kumar Giri (or) + * Ivar Smolin (et) + * Vladimir Melo (pt_BR) + * Matej Urban (sl) + * Khaled Hosny (ar) + * MiloÅ” Popović (sr) + * Funda Wang (zh_CN) + * Amitakhya Phukan (as) + * Baris Cicek (tr) + +================ +Evince 2.26.0 +================ + +Bug fixes: + + * Fix compiler warning when building with -Wformat (#574168, + Tobias Mueller) + * Fix a crash with encrypted documents (Christian Persch) + +Translation updates: + + * F Wolff (af) + * Martin Picek (cs) + * Fotis Tsamis (el) + * Ivar Smolin (et) + * Suso Baleato (gl) + * Yaron Shahrabani (he) + * Gabor Kelemen (hu) + * Francesco Marletta (it) + * Shankar Prasad (kn) + * Gintautas Miliauskas (lt) + * Sangeeta Kumari (mai) + * Ani Peter (ml) + * Sandeep Shedmake (mr) + * Manoj Kumar Giri (or) + * FabrĆ­cio Godoy (pt_BR) + * Mișu Moldovan (ro) + * Yuriy Penkin (ru) + * I. Felix (ta) + * Krishna Babu K (te) + +================ +Evince 2.25.92 +================ + +Bug fixes: + + * Show the 'jump to page' window in the right GdkScreen (#560541, + Carlos Garcia Campos) + * Make our own thumbnail when the provided one doesn't have the + needed size (#323198, #307357, Christian Spurk, Carlos Garcia Campos) + * Reduce the restriction on the minimum size of thumbnails from 40 + to 1 (#323198, Christian Spurk) + * Use GOption in thumbnailer (Carlos Garcia Campos) + * Fix saving a copy of encrypted PDF documents (#566791, Carlos + Garcia Campos) + * Don't use g_quark_from_static_string in a loadable module + (Christian Persch) + * Use g_object_unref() instead of gdk_pixbuf_unref() in impress + backend (#571707, Thomas H.P. Andersen) + +Translation updates: + + * Ihar Hrachyshka (be@latin) + * Alexander Shopov (bg) + * Gil Forcada (ca) + * Petr Kovar (cs) + * Christian Kirbach (de) + * David Lodge (en_GB) + * Juanje Ojeda Croissier (es) + * IƱaki LarraƱaga Murgoitio (eu) + * Ilkka Tuohela (fi) + * Claude Paroz (fr) + * Sweta Kothari (gu) + * Yuval Tanny (he) + * Takeshi AIHANA (ja) + * Changwoo Ryu (ko) + * Kjartan Maraas (nb) + * Duarte Loreto (pt) + * Daniel Nylander (sv) + * Chao-Hsiung Liao (zh_HK) + * Chao-Hsiung Liao (zh_TW) + +================ +Evince 2.25.91 +================ + +Bug fixes: + + * Fix several memory leaks (Christian Persch) + * Fix errors handling and propagation (Christian Persch) + * Fix several translation strings in properties dialog (#571787, + Nickolay V. Shmyrev) + * Do not resize the window on reloading (#571051, #304249, Carlos + Garcia Campos) + * Experimental introspection support (#569083, Christian Persch) + * Use g_set_error_literal() (Christian Persch) + * Other firxes for win32 (#339172, Hib Eris) + * Make session manager code compile for win32 (#339172, Carlos + Garcia Campos) + * Update totem-screensaver from totem svn to make it build for + win32 (#339172, Hib Eris) + * Make use of MateConf optional (#339172, Hib Eris, Carlos Garcia Campos) + * Fix i18n in libdocument (Christian Persch) + * Include gi18n-lib.h instead of gi18n.h in libs and backends + (Christian Persch) + +Translation updates: + + * Mikel GonzĆ”lez (ast) + * Ihar Hrachyshka (be@latin) + * Kenneth Nielsen (da) + * Dawa pemo (dz) + * Juanje Ojeda Croissier (es) + * Ivar Smolin (et) + * IƱaki LarraƱaga Murgoitio (eu) + * Gabor Kelemen (hu) + * Takeshi AIHANA (ja) + * Shankar Prasad (kn) + * Arangel Angov (mk) + * Wouter Bolsterlee (nl) + * Tomasz Dominikowski (pl) + * Taylon (pt_BR) + * Горан Ракић (sr) + * Daniel Nylander (sv) + * Theppitak Karoonboonyanan (th) + * Clytie Siddall (vi) + * Gan Lu (zh_CN) + * Chao-Hsiung Liao (zh_HK) + * Chao-Hsiung Liao (zh_TW) + +================ +Evince 2.25.90 +================ + +Bug fixes: + + * Split API documentation into libdocument, libview and shell + (#568465, Carlos Garcia Campos) + * Fix symbols conflict in impress backend (#569998, Christian Persch) + * Add mnemonics to buttons in search bar (#569212, Carlos Garcia + Campos) + * Fix a crash when printing (#569328, Carlos Garcia Campos) + * Fix --version command line option (#562869, Christian Persch) + * Use versioned directory for backends (#569082, Christian Persch) + * Add EV_DEFINE_BOXED_TYPE and EV_DEFINE_INTERFACE macros + (#568228, Carlos Garcia Campos) + * Sync EggToolbarEditor with libegg (Nickolay V. Shmyrev) + * Respect directory umask and setgid when saving files (#568593, + Michael J. Chudobiak) + * Add libtool versioning, pkg-config files and single headers for + libdocument and libview (#568224, #568220, #568229, #568227, + Christian Persch) + * Remove G_OBJECT casts from g_signal_connect calls (#568386, Hiroyuki + Ikezoe) + * Install several missing header files of libdocument and libview + (#567787, #567790, Tomeu Vizoso) + * Other bugs fixed: #569120, #569231, #569327, #570077 + +Translation updates: + + * Hendrik Richter (de) + * Ivar Smolin (et) + * Gabor Kelemen (hu) + * Changwoo Ryu (ko) + * Kjartan Maraas (nb) + * Daniel Nylander (sv) + +================ +Evince 2.25.5 +================ + +Code changes: + + * Move EvView specific code to a libevview library so that it can + be embbeded in other applications (#567751, Tomeu Vizoso, Carlos + Garcia Campos) + * Other improvements for embbeders (#567785, #567788, #567789, + Tomeu Vizoso) + +Bug fixes: + + * Fix a minor typo (Michal Vaner) + * Fix the API docs build (#568171, Christian Persch) + * Simplify drag data handling (#558084, Christian Persch) + * Several portability issues (#339172, Hib Eris) + * Fix mnemonic conflict (#567937, Carlos Garcia Campos) + * Other bugs fixed: #567910 + +Translation updates: + + * Gil Forcada (ca) + * Hendrik Richter (de) + * Juanje Ojeda Croissier (es) + * Kjartan Maraas (nb) + * Gan Lu (zh_CN) + +================ +Evince 2.25.4 +================ + +New Features and UI Improvements: + + * Show progress information when loading/saving remote files + (#370958, Carlos Garcia Campos) + * Remember page setup options (#525185, #349102, Carlos Garcia Campos) + * Show a confirmation dialog when there are pending print jobs + while closing the main window (#480964, Carlos Garcia Campos) + * Show progress information when printing (#482770, Carlos Garcia Campos) + * Add document URI to properties page (#555376, Carlos Garcia Campos) + +Bug fixes: + + * Bump glib requirement to fix build with older versions + (Christian Spurk) + * Fix mime type handling (Hib Eris) + * Fix saving images to remote locations (Carlos Garcia Campos) + * Do not try to jump to the location of the find result for every + match, but only for the first one. It makes searching really + faster. (#564774, Michael Hunold) + * Fix zip mime-type in comics backend (Juanjo MarĆ­n) + * Fix several memory leaks (Carlos Garcia Campos) + +Translation updates: + + * Ihar Hrachyshka (be@latin) + * Juanje Ojeda Croissier (es) + * Yuval Tanny (he) + * Kjartan Maraas (nb) + * Taylon (pt_BR) + * Gan Lu (zh_CN) + +================ +Evince 2.25.2 +================ + +New Features and UI Improvements: + + * PDF Optional content (layers) support. (Carlos Garcia Campos) + * The password dialog has been reworked in order to make it more + hig compliant and consistent (#562496, Carlos Garcia Campos) + * Improved keyring support. Password dialog is not shown anymore + when the password is already in the keyring (Carlos Garcia Campos) + * Support for monitor and reload of remote documents + (#555399, Carlos Garcia Campos) + +Bug fixes: + + * Fix opening comic documents with a wrong but valid extension + (#562143, Carlos Garcia Campos) + * Fix a crash when searching (#558377, Carlos Garcia Campos) + * Fix several crashes when selecting text (#561393, Marek KaŔík) + +Translation updates: + + * Jorge GonzĆ”lez (es) + * Nguyį»…n ĐƬnh Trung (vi) + +================ +Evince 2.25.1 +================ + +New Features and UI Improvements: + + * Use the message area instead of popup dialogs for any error and + warning messages (Carlos Garcia Campos) + +Bug fixes: + + * Adapt to the new single-include policy (#558064, Christian Persch) + * Fix page transition in presentation mode (#516749, Nickolay V. Shmyrev) + * Other bugs fixed: (#558066, Christian Persch) + +Translation updates: + + * Hendrik Richter (de) + +================ +Evince 2.24.1 +================ + +Bug fixes: + + * Disable toggle function of F5 when in presentation mode + (#556162, Dave Neary) + * Create a loading thumbnail for every different page size + (#556264, Carlos Garcia Campos) + * Fix several memory leaks (Carlos Garcia Campos) + * Do not show bad titles like 'Microsoft Word' in the window title + bar (#534684, Robin Sonefors and Nickolay V. Shmyrev) + * Fix mailto links which were considered as http uris (#555801, + Carlos Garcia Campos) + * Fix launching external applications (#554500, Carlos Garcia Campos) + * Other bugs fixed (#552382, #553369, #555134, Carlos Garcia Campos) + +Translation updates: + + * Ihar Hrachyshka (be@latin) + * Adrian GuniÅ” (cs) + * Kenneth Nielsen (da) + * Ivar Smolin (et) + * Takeshi AIHANA (ja) + +================ +Evince 2.24.0 +================ + +Bug fixes: + + * Use GdkAppLaunchContext when available to launch external uris + (Carlos Garcia Campos) + * Use the message area to show errors when lauching external uris + instead of a popup window (Carlos Garcia Campos) + * Assume invalid uris are http uris (#552071, Carlos Garcia + Campos) + +Translation updates: + + * Usama Akkad (ar) + * Gil Forcada (ca) + * Kenneth Nielsen (da) + * Hendrik Richter (de) + * nikosCharonitakis (el) + * Ilkka Tuohela (fi) + * Sweta Kothari (gu) + * Gabor Kelemen (hu) + * Francesco Marletta (it) + * Shankar Prasad (kn) + * Changwoo Ryu (ko) + * Gintautas Miliauskas (lt) + * Arangel Angov (mk) + * Santhosh Thottingal (ml) + * Sandeep Shedmake (mr) + * wadim dziedzic (pl) + * Mișu Moldovan (ro) + * Alexandre Prokoudine (ru) + * Горан Ракић (sr) + * Dr.T.Vasudevan (ta) + * Baris Cicek (tr) + * Chao-Hsiung Liao (zh_HK) + * Chao-Hsiung Liao (zh_TW) + +================ +Evince 2.23.92 +================ + +Bug fixes: + + * Allow page scrolling with PageUp/PageDown keys when find bar is + active (#529833, Carlos Garcia Campos) + * Search in page labels is now case-insensitive (#550136, Michael Lee) + * Do not append the file extension twice when saving an image + (Carlos Garcia Campos) + * Correctly restore the cursor after showing the context menu + (Carlos Garcia Campos) + * Grab the mediakeys with a low priority so that it doesn't + interfere with other applications like a music player + (#547164, Eric Piel) + +Translation updates: + + * Alexander Shopov (bg) + * Petr Kovar (cs) + * David Lodge (en_GB) + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * IƱaki LarraƱaga Murgoitio (eu) + * Robert-AndrĆ© Mauchin (fr) + * Ignacio Casal Quinteiro (gl) + * Kjartan Maraas (nb) + * Wouter Bolsterlee (nl) + * Leonardo Ferreira Fontenelle (pt_BR) + * Duarte Loreto (pt) + * Laurent Dhima (sq) + * Daniel Nylander (sv) + * Theppitak Karoonboonyanan (th) + * Clytie Siddall (vi) + * Funda Wang (zh_CN) + +================ +Evince 2.23.91 +================ + +New Features and UI Improvements: + + * Use EggSMClient from libegg instead of mate-client and remove + libmate and libmateui dependencies (Carlos Garcia Campos) + * Implement document_get_page_label in tiff backend (Carlos Garcia + Campos) + +Bug fixes: + + * Start up in the right workspace when resuming a previous session + (Carlos Garcia Campos) + * Fix marco warnings shown in ~/.xsession-errors (Carlos + Garcia Campos) + * Fix a crash due to an idle function called with an already + destroyed object (#549163, Carlos Garcia Campos) + * Fix problems when opening or saving attachments (#548653, Carlos + Garcia Campos) + * Handle document orientation in tiff backend (#548444, Carlos + Garcia Campos) + * Fix a crash when adjustment page size is 0 (#547440, Carlos + Garcia Campos) + * Other bugs fixed: #548462 + +Translation updates: + + * Runa Bhattacharjee (bn_IN) + * Gil Forcada (ca) + * Mario BlƤttermann (de) + * Dawa pemo (dz) + * Jorge GonzĆ”lez (es) + * IƱaki LarraƱaga Murgoitio (eu) + * Ilkka Tuohela (fi) + * Takeshi AIHANA (ja) + * Shankar Prasad (kn) + * Gintautas Miliauskas (lt) + * Arangel Angov (mk) + * Manoj Kumar Giri (or) + * Vladimir Melo (pt_BR) + * Chao-Hsiung Liao (zh_HK) + +================ +Evince 2.23.6 +================ + +Bug fixes: + + * Correctly build desktop file (#544237, Gƶtz Waschk) + +Other changes: + + * Cleanup #include statements. (Wouter Bolsterlee) + * The jobs system has been reworked in order to make it simpler + and easier to extend. (Carlos Garcia Campos) + * Add a profile mode available when debug is enabled (Carlos + Garcia Campos) + +Translation updates: + + * Djihed Afifi (ar) + * Adrian GuniÅ” (cs) + * Pierre Lemaire (fr) + * Duarte Loreto (pt) + +================ +Evince 2.23.5 +================ + +New Features and UI Improvements: + + * Intercept window manager's fullscreen request in order to + run/stop fullscreen mode (#493541, Carlos Garcia Campos) + * 7-zip compressed commics support (#532312, Kartik Rustagi) + * Add Alt+Left and Alt+Right shortcuts to rotate (#539972, Bastien + Nocera) + * Media player keys support (#539971, Bastien Nocera) + +Bug fixes: + + * Restart the job search when the find bar is opened for the + second time (#531956, Carlos Garcia Campos) + * Fixes caja crash caused by evince properties page extension + (#542548, Carlos Garcia Campos) + * Fix Solaris build (#542924, Darren Kenny) + * Fix endianess bug (#540950, Benjamin Jacobs) + * Other bugs fixed: #542001 + +Translation updates: + + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * Ignacio Casal Quinteiro (gl) + * Yuval Tanny (he) + * Rakesh Pandit (ks) + * Kjartan Maraas (nb) + * Wouter Bolsterlee (nl) + * Zabeeh Khan (ps) + * Daniel Nylander (sv) + * Theppitak Karoonboonyanan (th) + +================ +Evince 2.23.4 +================ + +New Features and UI Improvements: + + * A warning message is shown in the message area when the document + has no pages (#171588, Carlos Garcia Campos) + * Automaticly reload the document when the file has changed on + disk (#304249, Carlos Garcia Campos) + * Added Ctrl-Insert keybinding for copying text and Ctrl+N for + opening a copy (#526523, #532239, Carlos Garcia Campos) + * Save as action has been added as a toolbar button too. (Abhishek Mukherjee) + * Primary selection is also updated when copying a link address + (#520855, Carlos Garcia Campos) + * Added GtkMountOperation, copied from gtk+. (Carlos Garcia Campos) + * GS code has been removed, libspectre is now required to build the + PostScript backend. (Carlos Garcia Campos) + +Bug fixes: + + * Fixed a crash when opening documents with no pages + (#537574, Carlos Garcia Campos) + * Fixed a crash when running in debug mode (Daniel M. German) + * Fixed a crash in impress backend (#530852, Hans Petter Jansson) + * Improved error handling in djvu backend (#530202, Carlos Garcia Campos) + * Do not use the pixbuf backend for rendering tiff documents when + the tiff backend is available (#520290, Carlos Garcia Campos) + * Fixed T1 font rendering in dvi backend (#536883, Mattias Nissler) + * Other bugs fixed: #533323, #533897, #533896, #534493, #537535 + +Thanks: + + Matthias Drochner, Frederic Peters, Cosimo Cecchi + +Translation updates: + + * Djihed Afifi (ar) + + +================ +Evince 2.22.1.1 +================ + +Bug fixes: + + * Fix build in FreeBSD (#526799, Michael Johnson, Carlos Garcia Campos) + * Fixes djvu backend links (#526517, Carlos Garcia Campos) + +=============== +Evince 2.22.1 +=============== + +Bug fixes: + + * Add compressed dvi to the list of supported mime types + (#307087, Ed Catmur) + * Fix a crash when printing a range that doesn't specify the start + or end page (#524288, Carlos Garcia Campos) + * Fix a crash when thumbnail failed to render in PS backend + (#525015, Carlos Garcia Campos) + * Open default application when launching an external uri + (#525009, Emil Soleyman) + * Add a configure option to enable/diable DBus (#521797, RĆ©mi Cardona) + * Fix printing in documents with rotated pages (#512648, Eugen Dedu) + * Do not append file extension twice when saving an image + (#523069, Carlos Garcia Campos, Wouter Bolsterlee) + * Cancel find operation when the find bar is closed + (#508845, Carlos Garcia Campos) + * Several printing fixes: collate, reverse and copies were broken + (#365332, Eugen Dedu) + * Fix selection rendering when poppler is compiled with splash + backend (Carlos Garcia Campos). + * Other bugs fixed: #521224, #524112 + +Translation updates: + + * Alexander Nyakhaychyk (be) + * Yavor Doganov (bg) + * David Lodge (en_GB) + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * Clytie Siddall (vi) + +=============== +Evince 2.22.0 +=============== + +Bug fixes: + + * Do not print when Escape is pressed to close printing dialog + (#495107, Wouter Bolsterlee) + * Fix a crash in caja properties page (#519679, Carlos Garcia Campos) + * Fix a crash with transition effects (#519106, Carlos Garnacho) + * Fix document mime type detection (#518874, Carlos Garcia Campos) + * Fix a crash in forms (#518831, Carlos Garcia Campos) + * Fix images extraction (#516237, Carlos Garcia Campos) + +Translation updates: + + * Djihed Afifi (ar) + * Ihar Hrachyshka (be@latin) + * Petr Kovar (cs) + * Kenneth Nielsen (da) + * nikosCharonitakis (el) + * David Lodge (en_GB) + * Jorge GonzĆ”lez (es) + * Ivar Smolin (et) + * Ilkka Tuohela (fi) + * Claude Paroz (fr) + * Ignacio Casal Quinteiro (gl) + * Ankit Patel (gu) + * Yuval Tanny (he) + * Gabor Kelemen (hu) + * Luca Ferretti (it) + * Takeshi Aihana (ja) + * Shankar Prasad (kn) + * Changwoo Ryu (ko) + * Gintautas Miliauskas (lt) + * Arangel Angov (mk) + * Sandeep Shedmake (mr) + * Narayan Kumar Magar (ne) + * ƅsmund SkjƦveland (nn) + * Yannig Marchegay (Kokoyaya) (oc) + * Hugo Doria (pt_BR) + * Laurent Dhima (sq) + +=============== +Evince 2.21.91 +=============== + +Bug fixes: + + * mate-print code has been removed (#512370, Carlos Garcia Campos) + * Fix thumbnailer crash (#513934, Matthias Clasen) + * Fix djvu rendering (#482720, Carlos Garcia Campos) + * Fix mime type detection based on file contents (Cosimo Cecchi, Carlos Garcia Campos) + * Other bugs fixed: #512719, #512720, #512718, #512771, #513203 + +Translation updates: + + * Khaled Hosny (ar) + * Amitakhya Phukan (as) + * Hendrik Richter (de) + * Jorge GonzĆ”lez (es) + * Kjartan Maraas (nb) + * Yannig Marchegay (Kokoyaya) (oc) + * Tomasz Dominikowski (pl) + * Vladimir Melo (pt_BR) + * Duarte Loreto (pt) + * Theppitak Karoonboonyanan (th) + * Serdar CICEK (tr) + * Woodman Tuen (zh_HK) + +=============== +Evince 2.21.90 +=============== + +Important notice for distributors: + + * This release overrides document permissions by default. See + http://bugzilla.mate.org/show_bug.cgi?id=382700 for more information + about this. + * Ghostscript interface for PostScript documents is now deprecated. + Please use libspectre (http://libspectre.freedesktop.org/) + instead. + +New features and UI improvements: + + * Port to gio and drop mate-vfs dependency. (#510401, Cosimo Cecchi, Carlos Garcia Campos) + * Scroll pages in page entry with mouse wheel (#324122, David Turner) + * Automatic scrolling in context menu (#323670, David Turner) + * Kinetic scrolling (#461271, David Turner) + * New plugin system for backends (#351348, Carlos Garcia Campos) + * Allow saving images in formats other than PNG (#500209, Carl-Anton + Ingmarsson, Nickolay V. Shmyrev) + * Add support for page transitions (#458460, Carlos Garnacho) + * Override document restrictions by default (#382700, Wouter Bolsterlee) + * Use up/down arrows instead of left/right (#170081, Wouter Bolsterlee) + +Bug fixes: + + * Reset cursor correctly (#501603, #509958, Carlos Garcia Campos) + * Use GSlice to allocate memory (#475972, Christian Persch) + * Do not limit minimum zoom factor in best fit/fit width mode (#503805, + Carlos Garcia Campos) + * Use libspectre, if available, for the ps backend. (#317106, #499787, + #501235, #421879, #445797, #443859, #486547, #386005, #507705, Carlos Garcia Campos) + * Put RTL marker in recent file list (#509076, Djihed Afifi) + * Put Caja extension in right place (#505359, Matthias Clasen, Brian + Pepple, Damien Carbery, Wouter Bolsterlee) + * Avoid filename quoting issues (#502500, Tom Parker) + * Repair horizontal scrolling with shift (#483412, Nickolay V. Shmyrev) + * Mate Keyring is now an optional dependency (#509676, Marcelo Lira) + * Reduce memory consumption by rendering images on demand (Carlos Garcia Campos) + * Fix printing in dvi backend when filename contains white spaces. (#502839, Carlos Garcia Campos) + * Other bugs fixed: #502843, #460658, #504721, #511635 + +Thanks: + + * Carlos Garcia Campos + * Christian Persch + * Yaakov Selkowitz + * David Turner + * Carl-Anton Ingmarsson + * Carlos Garnacho + * Djihed Afifi + * Nickolay V. Shmyrev + * Wouter Bolsterlee + * Marcelo Lira + +Translation updates: + + * Djihed Afifi (ar) + * Ihar Hrachyshka (be@latin) + * Gil Forcada (ca) + * Jorge Gonzalez (es) + * Priit Laes (et) + * Inaki Larranaga Murgoitio (eu) + * StĆ©phane Raimbault (fr) + * SeĆ”n de BĆŗrca (ga) + * Ignacio Casal Quinteiro (gl) + * Kjartan Maraas (nb) + * Wouter Bolsterlee (nl) + * Thierry Randrianiriana (mg) + * Yannig Marchegay (oc) + * Leonardo Ferreira Fontenelle, Hugo Doria, Rodrigo Flores (pt_BR) + * Daniel Nylander (sv) + * Clytie Siddall (vi) + +=============== +Evince 2.21.1 +=============== + +New Features and UI Improvements: + + * Use message area for notification about errors + * Some print settings for documents are stored in metadata + * Added command line option for search + * Expander with a recent documents is added to toolbar + * Egg-recent code dropped + * Djvu, dvi and comics are enabled by default + * Support for links with URI's like mailto (Pascal Terjan) + * Removed the shadow of the fullscreen toolbar + +Bug fixes: + + * A lot of bug fixes from a stable branch + +Thanks: + + To Carlos Garcia Campos who did the all the amazing work above :) + +Translations: + + Changwoo Ryu (ko) + Daniel Nylander (sv) + Djihed Afifi (ar) + Jorge Gonzalez (es) + Francesco Marletta (it) + Gabor Kelemen (hu) + Gintautas Miliauskas (lt) + Ignacio Casal Quinteiro (gl) + Ihar Hrachyshka (be@latin) + Ilkka Tuohela (fi) + Ivar Smolin (et) + Jorge GonzĆ”lez (es) + Kjartan Maraas (nb) + Matej Urbančič (sl) + Peter Bach (da) + Yannig Marchegay (Kokoyaya) (oc) + +=============== +Evince 2.20.2 +=============== + +Bug fixes: + + * Tiff documents were rendered with wrong colors (Matthias Clasen) + * Fix endless loop when zero pages per sheet is selected (Darren Kenny) + * Do not jump to the first page when reloading from command line (Carlos Garcia Campos) + * Memory leak fix (Hiroyuki Ikezoe) + * Fix printing with poppler splash backend (Carlos Garcia Campos) + * Fix a crash when printing with the mate-print dialog (Carlos Garcia Campos) + * Warning fix (Sebastien Bacher) + +Translations: + + Matej Urbančič (sl) + Youssef Chahibi (ar) + +=============== +Evince 2.20.1 +=============== + +Bug fixes: + + * Printing multiple pages per sheet fixes (Carlos Garcia Campos) + * Respect ranges order during print (Carlos Garcia Campos) + * Silently ignore unknown form fields (Matthias Drochner) + * Do not show the toolbar in fullscreen mode if the main toolbar is not visible (Carlos Garcia Campos) + * Give priority to form fields over images (Carlos Garcia Campos) + +Translations: + + Changwoo Ryu (ko) + Francesco Marletta (it) + Ilkka Tuohela (fi) + Peter Bach (da) + +=============== +Evince 2.20.0 +=============== + +Bug fixes: + + * Forms support broken by forgotten ifdef fix (Carlos Garcia Campos) + * Issue with making a copy and xfer to different file system (Carlos Garcia Campos) + * Desktop file fixes (Gƶtz Waschk) + +Translations: + + Youssef Chahibi (ar) + Alexander Shopov (bg) + Peter Bach (da) + Ivar Smolin (et) + Ignacio Casal Quinteiro (gl) + Francesco Marletta (it) + Gintautas Miliauskas (lt) + Espen Stefansen (nb) + Yannig Marchegay (Kokoyaya) (oc) + Mişu Moldovan (ro) + Peter TuhĆ”rsky (sk) + Laurent Dhima (sq) + Горан Ракић (sr) + Maxim Dziumanenko (uk) + +=============== +Evince 2.19.92 +=============== + +Bug fixes: + + * Bump requirements to poppler 0.6 (Carlos Garcia Campos) + * Restart the search when reloading a document with the find bar visible (Carlos Garcia Campos) + * Do not jump to the first page when reloading a document from the last page (Carlos Garcia Campos) + * Do not update visibility when changing a chrome flag (Carlos Garcia Campos) + * Use GTK_UNIT_POINTS instead of GTK_UNIT_PIXEL (Carlos Garcia Campos) + * Make sure current-page <= end-page in continuous mode (Carlos Garcia Campos) + * Ignore outline items without a title (Carlos Garcia Campos) + * Use only drag mouse cursor during a drag operation (Carlos Garcia Campos) + * Actually fix printing regressions (Carlos Garcia Campos) + * Add support for double and triple click selections (Carlos Garcia Campos) + +Translations: + + Gil Forcada (ca) + +=============== +Evince 2.19.4 +=============== + +Bug fixes: + + * Find works when findbar is hidden (Justin Blanchard) + * Use directly cairo_surface_get_content (Jeff Muizelaar) + * Great speedup by using CAIRO_FORMAT_RGB24 instead of CAIRO_FORMAT_ARGB32 (Jeff Muizelaar) + * Fix for embedded thumbnails dimension and rotation (Carlos Garcia Campos) + * Avoid ifdefs inside macros (Jens Granseuer) + * Memory leak fixes (Justin Blanchard, Hiroyuki Ikezoe) + * New icon sizes (Andreas Nilsson) + * Invalid read fix (Justin Blanchard) + * Fix multihead problems in toolbar editor (Carlos Garcia Campos) + * Use the same cairo context for every page in the same expose event (Carlos Garcia Campos) + +Translations: + + Adam Weinberger (en_CA) + Danishka Navin (si) + Duarte Loreto (pt) + Hendrik Richter (de) + Ihar Hrachyshka (be@latin) + Raphael Higino (pt_BR) + StĆ©phane Raimbault (fr) + Takeshi Aihana (ja) + +============== +Evince 0.9.3 +============== + +New Features and UI Improvements: + + * Use new GTK+ tootlips (Carlos Garcia Campos) + * Printing multiple pages per sheet (Carlos Garcia Campos) + * Added UNIX-like key bindings hjkl (Cosimo Cecchi) + +Bug fixes: + + * Do not block while enumerating printers in preview mode (Christian Persch) + * Show new pages as soon as they are rendered even if other page + elements like forms, images or links are not ready yet (Carlos Garcia Campos) + * Provide different options in the print dialog depending on document + type backend capabilities (Carlos Garcia Campos) + +Translations: + + Jorge GonzĆ”lez (es) + IƱaki LarraƱaga Murgoitio (eu) + Ilkka Tuohela (fi) + Gabor Kelemen (hu) + Arangel Angov (mk) + Wouter Bolsterlee (nl) + Funda Wang (zh_CN) + +============== +Evince 0.9.2 +============== + +New Features and UI Improvements: + + * Forms support (Julien Rebetez, Carlos Garcia Campos) + * Toolbar editor icons on dragging (Jaap Haitsma) + +Bug fixes: + + * Program description translation issue fixed (Gabor Kelemen) + * Do not change page after presentation (Cesar Fernandez) + +Translations: + + Ankit Patel (gu) + Arangel Angov (mk) + Bharat Kumar (te) + Clytie Siddall (vi) + Daniel Nylander (sv) + Dr.T.Vasudevan (ta) + Espen Stefansen (nb) + Ivar Smolin (et) + Takeshi Aihana (ja) + Theppitak Karoonboonyanan (th) + Tomasz Dominikowski (pl) + Tshewang Norbu (dz) + +============== +Evince 0.9.1 +============== + +New Features and UI Improvements: + + * Cairo-based renderer for pages and selections of PDF, DVJU and DVI documents (Carlos Garcia Campos) + * PDF exporter for printing DVI documents (Alaska Subedi) + * Zoom icon artwork (Michael Monreal) + * Patch to avoid frequent process wakeup for metadata handling (Bastien Nocera, Arjan van de Ven) + * Bumped poppler requirements to 0.5.9 (Carlos Garcia Campos) + * Changed sidebar ordering (Wouter Bolsterlee) + * Removed extra locking of a main loop to speed up rendering (Carlos Garcia Campos) + +Bug fixes: + + * Print job is released quickly (Carlos Garcia Campos) + * Update a cursor and tooltips after scrolling (Carlos Garcia Campos) + * More safe mkdir_with_parents (Eduardo Lima) + * Safe saving of a document copy to a remote location (Carlos Garcia Campos) + * Build fix without libmate (Marc Brockschmidt) + * Selections don't disappear after zoom (Carlos Garcia Campos) + * Preview button added to the print dialog (Carlos Garcia Campos) + +Translations: + + Daniel Nylander (sv) + David Lodge (en_GB) + Dr.T.Vasudevan (ta) + Espen Stefansen (nb) + Gil Forcada (ca) + Ignacio Casal Quinteiro (gl) + Ivar Smolin (et) + Jorge GonzĆ”lez (es) + Sonam Pelden (dz) + Theppitak Karoonboonyanan (th) + Yuval Tanny (he) + +============== +Evince 0.9.0 +============== + +New Features and UI Improvements: + + * Printing support in djvu documents (Alaska Subedi) + * Optional drop of libmate dependency (Ross Burton) + * Print button for preview mode (Carlos Garcia Campos) + * Remember print settings (Carlos Garcia Campos) + * Use gtk+ builtin paper list to identify the document's paper size (Carlos Garcia Campos) + * Start rendering pages before any other jobs (Carlos Garcia Campos) + * Developer documentation updated. (IƱigo MartĆ­nez) + * History button improvements (Nickolay Shmyrev) + +Bug Fixes: + + * Fixes for issues with fullscreen toolbar (Carlos Garcia Campos) + * Open document in empty window with EggRecent (Eduardo Lima) + * Compilation fixes for FreeBSD (Roy Marples) + * A lot of calls to parent class methos added (Christian Persch) + * Update gtk-icon-cache in uninstall hook (Brian Pepple) + * Fix for crash in comics backend when filename contains quote (Nickolay Shmyrev) + * Always add application-specific icons to the theme (Nickolay Shmyrev) + +Translations: + + Jakub Friedl (cs) + Dimitris Glezos (el) + David Lodge (en_GB) + Jorge GonzĆ”lez (es) + Ivar Smolin (et) + Francesco Marletta (it) + Espen Stefansen (nb) + Yannig Marchegay (oc) + Washington Lins (pt_BR) + Daniel Nylander (sv) + +============== +Evince 0.8.1 +============== + +Bug Fixes: + + * Default resolution is used when it's not provided by TIFF + document (Carlos Garcia Campos) + * Added support for ps, eps and compressed documents + thumbnails (Carlos Garcia Campos) + * Implemented font color specials in the DVI backend (Ricardo Markiewicz) + * DOCUMENTS folder from xdg-data-dirs is used as default folder when + opening a file chooser (Matthias Clasen) + * unlink-tempfile is used instead of unlink-temp-file (Carlos Garcia Campos) + +Translations: + + Gil Forcada (ca) + Ignacio Casal Quinteiro (gl) + IƱaki LarraƱaga Murgoitio (eu) + Jorge GonzĆ”lez (es) + Jakub Friedl (cs) + Sonam Pelden (dz) + +============== +Evince 0.8.0 +============== + +Features and UI Improvements: + + * Thumbnails for postscript documents (Carlos Garcia Campos) + * Copy of compressed document is saved compressed (Carlos Garcia Campos) + +Bug Fixes: + + * Properly handle PostScript page orientation (Carlos Garcia Campos) + * Do not render thumbnails when sidebar thumbnail page is not visible + on startup (Carlos Garcia Campos) + * Warning when document has one page (Carlos Garcia Campos) + * Djvu backend builds on Mac (Carlos Garcia Campos) + * Allow deleting in goto window in presentation mode (Carlos Garcia Campos) + +Translations: + + Ankit Patel (gu) + Changwoo Ryu (ko) + Christian Kintner (de) + Clytie Siddall (vi) + Daniel Nylander (sv) + Djihed Afifi (ar) + Duarte Loreto (pt) + Erdal Ronahi (ku) + Funda Wang (zh_CN) + Gabor Kelemen (hu) + Gil Forcada (ca) + Gintautas Miliauskas (lt) + Ihar Hrachyshka (be) + Ivar Smolin (et) + Kjartan Maraas (nb) + Matic Žgur (sl) + Maxim Dziumanenko (uk) + Mişu Moldovan (ro) + Nickolay V. Shmyrev (ru) + Raphael Higino (pt_BR) + Takeshi Aihana (ja) + Thierry Randrianiriana (mg) + Wadim Dziedzic (pl) + Woodman Tuen (zh_HK) + Woodman Tuen (zh_TW) + Yuval Tanny (he) + Š˜Š³Š¾Ń€ ŠŠµŃŃ‚Š¾Ń€Š¾Š²ŠøŃ› (sr) + +============== +Evince 0.7.2 +============== + +New Features and UI Improvements: + + * Document thumbnail is used as window icon (Carlos Garcia Campos) + * Support for gzipped and bzipped + document types (Carlos Garcia Campos) + * A lot of new icons (Luca Feretti, Andreas Nilsson) + * Finally working history button (Nickolay V. Shmyrev) + +Internal Improvements: + + * Developer gtk-doc based documentation (Nickolay V. Shmyrev) + * More dogtail tests (Nickolay V. Shmyrev) + +Bug Fixes: + + * Fontconfig lock to avoid crashes (Carlos Garcia Campos) + * Critical crash fix by checking return value from + psscan (Tom Parker) + * Critical crash fix in caja property page and + broken ODF (Tom Parker) + * Critical crash fix with multifile djvu + documents (Carlos Garcia Campos) + * Multithreaded gtk problems fix by using async + mate-vfs (Carlos Garcia Campos) + +Translations: + + Changwoo Ryu (ko) + Clytie Siddall (vi) + Daniel Nylander (sv) + David Lodge (en_GB) + Dimitris Glezos (el) + Dorji Tashi (dz) + Duarte Loreto (pt) + Gabor Kelemen (hu) + Ihar Hrachyshka (be) + Ilkka Tuohela (fi) + Ivar Smolin (et) + Jovan Naumovski (mk) + Khaled Hosny (ar) + Kjartan Maraas (nb) + Nickolay V. Shmyrev (ru) + StĆ©phane Raimbault (fr) + Theppitak Karoonboonyanan (th) + Vladimir Petkov (bg) + Wouter Bolsterlee (nl) + Yuval Tanny (he) + +Thanks: + + Ed Catmur, Esteban Sanchez, Theppitak Karoonboonyanan, Wouter Bolsterlee + +============== +Evince 0.7.1 +============== + +New Features: + + * Popup window to jump to another page in presentation mode. (Carlos Garcia Campos) + * Page transition support in presentation mode. (Carlos Garcia Campos) + +Bug Fixes: + + * Fix random crashes related to threads and Font Config (Carlos Garcia Campos) + * Fix icon name in desktop file (Luca Ferretti) + * Always use "best fit" zoom in presentation mode (Wouter Bolsterlee) + * Don't restore the view on the last page when a document is reopened + (Wouter Bolsterlee) + * Fix build due to libxml2 (Elijah Newren) + +Translations: + + * Djihed Afifi (ar) + * Ales Nyakhaychyk (be) + * Alexander Shopov (bg) + * Josep Puigdemont i Casamajó (ca) + * David Lodge (en_GB) + * Francisco Javier F. Serrador (es) + * Priit Laes (et) + * Raivis Dejus (lv) + * Jovan Naumovski (mk) + * Kjartan Maraas (nb) + * Wouter Bolsterlee (nl) + * Matic Žgur (sl) + * Daniel Nylander (sv) + * Theppitak Karoonboonyanan (th) + * Clytie Siddall (vi) + +Thanks: + + Damien Carbery, Jens Granseuer, Yevgen Muntyan + +============== +Evince 0.7.0 +============== + +New Features: + + * History. Preliminary implementation, not finished yet. (Nickolay V. Shmyrev) + * Option to open a copy of the document in a new window. (Carlos Garcia Campos) + * Multihead systems support. (Carlos Garcia Campos) + * Added paper size field to the properties dialog. (Kasper Svendsen, Wouter Bolsterlee) + * Added automated UI tests with dogtail (Nickolay V. Shmyrev) + +Bug Fixes: + + * Links support in rotated documents. (Radoslav Dorcik) + * Side pane disabled when entering and leaving fullscreen mode. (Nickolay V. Shmyrev) + * Fixed crash in DVI backend. (Nickolay V. Shmyrev) + * Printing settings scale, copies, collate, reverse, odd/even work again (Carlos Garcia Campos) + * Added page setup dialog for printing. (Carlos Garcia Campos) + * Support for printing to a PDF file. (Carlos Garcia Campos) + * Allow blank screen in presentation mode. (Carlos Garcia Campos) + * Added "End of presentation" page in presentation mode. (Carlos Garcia Campos) + * Screensaver detection in presentation mode. (Wouter Bolsterlee) + * "Loading..." text is not shown in presentation mode. (Wouter Bolsterlee) + +Security Fixes: + + * Buffer overflow in PS backend. CVE-2006-5864. (Carlos Garcia Campos) + +Translations: + + * Djihed Afifi (ar) + * Runa Bhattacharjee (bn_IN) + * Gil Forcada (ca) + * Jakub Friedl (cs) + * Mindu Dorji (dz) + * Dimitris Glezos (el) + * David Lodge (en_GB) + * Francisco Javier F. Serrador (es) + * Ilkka Tuohela (fi) + * Claude Paroz (fr) + * Ankit Patel (gu) + * Rajesh Ranjan (hi) + * Gabor Kelemen (hu) + * Satoru SATOH (ja) + * Vladimer Sichinava (ka) + * Fano Rajaonarisoa (mg) + * Kjartan Maraas (nb) + * Wouter Bolsterlee (nl) + * Leonardo Ferreira Fontenelle (pt_BR) + * Leonid Kanter (ru) + * Steve Murphy (rw) + * Daniel Nylander (sv) + * Theppitak Karoonboonyanan (th) + * Baris Cicek (tr) + +Thanks: + + Claudio Saavedra, Gabriel Felipe Cornejo, Mathias Hasselmann, Michael Monreal, + Julien Rebetez, P. Henrique Silva, Yevgen Muntyan + +============== +Evince 0.6.1 +============== + +Bug Fixes: + + * Current page follows find page + * Reopen reloads document + * Document size is guessed correctly + * Icons are installed in correct folders + * Memory leak fixed, etc + +Translations: + + * da dz el en_GB es fa gl it ka nn ro + * documentation: fi it sv + +Thanks: + + Carlos Garcia Campos, Mathias Hasselmann, Wouter Bolsterlee, + Christian Persch + + and especially translators: + + Alessio Frusciante, ƅsmund SkjƦveland, David Lodge, + Francisco Javier F. Serrador, Ignacio Casal Quinteiro, Kostas Papadimas, + Mugurel Tudorm, Ole Laursen, Pema Geyleg, Roozbeh Pournader, + Vladimer Sichinava, Daniel Nylander, Tommi Vainikainen, Luca Ferretti + +============== +Evince 0.6.0 +============== + +Bug Fixes: + + * Links aren't lost while resizing + * Tiff backend crash is fixed + * Recent code and icons code are screen safe + * Crash while handling password-protected documents is fixed + +Translations: + + * bg bn bn_IN ca de dz el et fa fi fr gu hi hu ja ko lt lv mk ml + mr nb or pt pt_BR ro ru sl sv th tr vi zh_CN zh_HK zh_TW + +Thanks: + + Carlos Garcia Campos, Julien Rebetez, Sebastien Bacher, Wouter Bolsterlee + + and especially translators: + + Abel Cheung, Alexander Shopov, Ani Peter, Ankit Patel, Baris Cicek, + Changwoo Ryu, Chao-Hsiung Liao, Christophe Merlet, Claude Paroz, Clytie + Siddall, Daniel Nylander, Duarte Loreto, Funda Wang, Gabor Kelemen, + Guntupalli Karunakar, Hendrik Richter, Ilkka Tuohela, Ivar Smolin, Josep + Puigdemont i Casamajó, Jovan Naumovski, Khandakar Mujahidul Islam, + Kjartan Maraas, Leonardo Ferreira Fontenelle, Leonid Kanter, Lucas + Rocha, Matic Žgur, Meelad Zakaria, Mişu Moldovan, Mugurel Tudor, Nikos + Charonitakis, Priit Laes, Rahul Bhalerao, Raivis Dejus, Rajesh Ranjan, + Roozbeh Pournader, Runa Bhattacharjee, Satoru SATOH, Subhransu Behera, + Theppitak Karoonboonyanan, Žygimantas Beručka + +============== +Evince 0.5.5 +============== + +New Features: + + * Evince have preview mode for using with gtk+ + * New print dialog is used with gtk 2.10 + * New recent files framework is used + +UI Improvements: + + * Sidebar is unified with Epiphany + * Escape unfocus page entry + +Bug Fixes: + + * Automake updates + * Link coordinates now used correctly + * Desktop file is corrected + * Memory leak fixes + +Translations: + + * ca cs de dz el es et eu fi fr gu ja ko mk nl ru sl sv th zh_HK zh_TW + +Thanks: + + Carlos Garcia Campos, Christian Persch, Radoslav Dorcik, + Ricardo Markiewicz, Sebastien Bacher, Wouter Bolsterlee + + and especially translators: + + Ankit Patel, Arangel Angov, BenoĆ®t Dejean, Changwoo Ryu, Chao-Hsiung + Liao, Christophe Merlet, Daniel Nylander, Francisco Javier F. Serrador, + Guntupalli Karunakar, Hendrik Richter, Ilkka Tuohela, Inaki Larranaga, + Jakub Friedl, Josep Puigdemont i Casamajó, Jovan Naumovski, Nikos + Charonitakis, Priit Laes, Takeshi Aihana, Theppitak Karoonboonyanan + +============== +Evince 0.5.4 +============== + +New Features: + + * Evince supports impress slides with --enable-impress + * URI's with anchors can be opened + +Bug Fixes: + + * PAPI printing crash was fixed + * Threads-related crash was fixed + * Assertion on document types that don't support + links don't cause crash now + * Reload crash was fixed as loading of + password-protected documents + * Page-label command line option works again + +Translations: + + * be bg bn_IN ca cs de dz es et eu fi fr gl gu he id ko mk nb ne + nl ru ta th vi zh_CN zh_HK zh_TW + +Thanks: + + Bastien Nocera, Carlos Garcia Campos, Ghee Teo, Pat Rondon, + Ricardo Markiewicz, Wouter Bolsterlee + + and especially translators: + + Ahmad Riza H Nst, Ales Nyakhaychyk, Alexander Shopov, Ankit Patel, + BenoĆ®t Dejean, Changwoo Ryu, Chao-Hsiung Liao, Clytie Siddall, + Francisco Javier F. Serrador, Funda Wang, Hendrik Richter, + I. Felix, Ignacio Casal Quinteiro, Ilkka Tuohela, Inaki Larranaga, + Jakub Friedl, Jayaradha, Josep Puigdemont Casamajó, + Jovan Naumovski, Kjartan Maraas, Pawan Chitrakar, Pema Geyleg, + Priit Laes, Runa Bhattacharjee, Theppitak Karoonboonyanan, + Vincent van Adrighem, Yair Hershkovitz + +============== +Evince 0.5.3 +============== + +New Features: + + * Evince supports attachments in PDF files + * We require gtk 2.8 and djvulibre 3.5.17 now + * Links system is reworked allowing named links + +Interface imporovements: + + * Esc closes properties dialog + * Cursor is hidden in presentation mode + * Find previous item is added to the menu + +Bug Fixes: + + * GOption port and po/LINGUAS work + * Fixed mime type detection by file contents + * Several memory leaks were found + * Crash on exit related to thumbnailer + * Current page is selected more intelligently + * Work around libtool build bug is created + +Translations: + + + * bg bn br dz es et fi gl gu nb nl nn no ro vi zh_CN zh_HK zh_TW + +Thanks: + + Michael Plump, Carlos Garcia Campos, Wouter Bolsterlee, Francisco + Javier F. Serrador, Kjartan Maraas and especially all translators. + +============== +Evince 0.5.2 +============== + +Bug Fixes: + + * View don't jump on scrolling + * In presentation mode we scroll by one page + +Translations: + + * bg bn da de et fa fr hi hu ja ka pt pt_BR ro ru sv uk vi + +============== +Evince 0.5.1 +============== + +New Features: + + * Evince can search text in DJVU files + +Interface improvements: + + * Odd pages are placed to the right in dual page mode + +Bug Fixes: + + * Produce thumbnails for comic books and carefully work with + unzip + * Correctly interoperate with DBUS 0.60 + * Ghostscript interpreter is checked in runtime + * PS mime types are listed in desktop file + * It's possible to add sidebar checkbutton on toolbar + * It's possible to print in inverse order + * Filechooser now will ask for overwrite confirmation + * Search now takes account for scrolling + * It's allowed to scroll view from find entry + * Evince don't jump to find result if window was scrolled during search + * It's now clear what page the toolbar page number applies to + +Translations: + + * bg ca cs cy da el en_CA es et eu fi gl gu he hu ja lt nb nl no pl + pt_BR pt ru sq sr th vi zh_CN zh_HK zh_TW + * Spanish docs are updated and we have Bulgarian translation now + +Thanks: + + Ricardo Markiewicz, Rostislav Raykov, Stanislav Slusny, + Francisco Javier F. Serrador, Antoine Dopffer, Michael Hofmann, + Gary Coady, Luca Ferretti, Carlos Garcia Campos, Eduardo de Barros Lima, + Christopher Aillon and all translators. + +============== +Evince 0.5.0 +============== + +New Features: + + * Various types of PDF links are now supported + * New backend for comic books (CBR/CBZ archives) + * Storage of passwords for protected documents in mate-keyring + * Layout settings for new documents are taken from the last used document + +Interface Improvements: + + * Statusbar was removed to save space + * Notification about page processing is shown on the page + * More key bindings + * Improved intelligent window title that shouldn't confuse users + * Show index by default if available + * Tooltips for links + * Now we have right click context menu + +Bug Fixes: + + * Crashes on reload and window close are fixed + * DBus usage cleaned + * Printing of multiple copies start to work + * Crash on structured ps document + * Improved session handling + * Vertical and horizontal scrolling on selection + +Translations: + + * be, bg, bn, ca, cs, de, en_CA, es, et, eu, fi, fr, gl, gu + hu, it, ku, lt, nb, nl, no, ro, ru, sr, sv, th, vi, zh_CN, zh_HK, zh_TW + * nl for documentation. + +============== +Evince 0.4.0 +============== + +Bug Fixes: + + * Depend on poppler 0.4.0 + * Pass in timestamp to to handle opening multiple windows over dbus + better (Ryan Lortie) + * Really quit when rendering postscript files. + * Set textdomain to libglade. + * Fix rotation of thumbnails multiple times + * Fix selection for non-continuous case + * Disable selection and links for rotated case + * findbar fixes + +============== +Evince 0.3.4 +============== + +Bug Fixes: + + * Depend on poppler 0.4.0 + * Convert filenames to utf8 when showing them in the UI + * Return focus back to view on escape + * Fix compatibility with dbus 0.34.2 + * Fix some build warnings + * Fix dvi backend compilation with t1lib-5.0 + +============== +Evince 0.3.3 +============== + +New Features: + + * real text selection, complete with theming and I-Beam support + * Respect lockdown mateconf keys + * Metadata support per document + * Font properties dialog + * dbus support to create a factory + * Implement caja plugin for properties. + +Interface Improvements: + + * HIG Fixes + * Hide menu entry in .desktop file + * Get the correct mime types in the open dialog + +Bug Fixes: + + * Improved rotation support + * Backwards searching + * Use unzipped document when printing + * Persist the print settings + * Support pdf's with 0 pages + * Fix find flickering + * mate-doc-utils for manual + * Add keypad accelerators for zooming. Fix for bug 308128. + * fix mem leaks + * MIME Fixes + * tiff/djvu backend fixes + +Translations: + + * eu, gl, gu, he, hi, ne, pa, pl, sk, sr, st@Latn, tr + +============== +Evince 0.3.2 +============== + +New Features: + + * Multi page tiff support + * Document properties + * Document rotation + * Mate vfs support + +Interface Improvements: + + * Faster dvi rendering + * More compact layout for documents with different page formats + * Stock icons for all toolbar editor items + * Multiple selection in the open dialog + * Loading progress feedback + * Multiple column thumbnails + * Expand links when specified by the document + * Several new handy keybindings + * Ellipsize recent items labels + +Bug Fixes: + + * Fix rendering of several postscript documents + * Fix reloading of postcript documents + * Fix a lot of memory leaks + * Fix compilation on gcc 4 + * Several minor fixes + +============== +Evince 0.3.1 +============== + +New Features: + + * Toolbar editor + * Middle button scrolling + * Support for printing page ranges + * Support for printing chapters (from the index) + +Interface Improvements: + + * Move best fit items in the toolbar control at the top + * In the sidebar make drop down menu as large as the button + * Several keybindings improvements + * Faster scrolling + * Only render the visible thumbnails (less memory usage) + * Get rid of confusing Normal size menu + +Bug Fixes: + + * Better mime detection + * Several minor fixes + * Fix printing on LPD printers + +============== +Evince 0.3.0 +============== + +New Features: + + * Continous mode + * Dual page mode + * Control + Scroll does zooming + * Shift + Scroll scrolls horizontally + * Zoom control in the toolbar + +Interface Improvements: + + * If all page labels are numeric show only + the page total + +Bug Fixes: + + * Fix size of dvi thumbnails + * Fix crash on exit + * Make thumbnailer schemas translatable + * Fix build problems with t1lib + +============== +Evince 0.2.1 +============== + +New Features: + + * new backend to support DJVU files. + * new backend to support DVI. + * Initial implementation of doc metadata. No UI for this yet, + though we respect docs requesting UI changes. + * support application/x-gzpostscript + * consolidate mime handling code so thumbnailer and main page + can use them. Thumbnail all types we support. + * Respect document orientation + * Implement print and save a copy for the ps backend. + * Badass completion of the document in the entry. Only works + with GTK+ >= 2.7.1 + * User docs + +Bug Fixes: + + * Faster Zooming + * Clean up Schemas + * README added. + * Small rework of sidebar code. Now sidebar pages should implement + EvSidebarPage interface. + * lots of small bug fixes + * Actually fix the "FIXME: update priority" comment. + * Update egg-recent from libegg. This should fix long startup + problem. + +Interface Improvements: + + * Shift + Space scrolls up. + * Removed fullscreen toolbar hard-coded style + * Fix normal size zoom. Make zoom in/zoom out unsensitive when + they are behyond the limits. + * Doesn't show resize grip when maximized + * don't draw border in fullscreen + * Doesn't show thumbnails if document has 1 page. Make sidebar + menu entries sensitive only if if they have sense. + * Work around the Escape conflict (find bar and unfullscreen) + * Save accelerators on exit. + * Add Edit->Find Next + +Translations: + + * Make all toolbar items translatable + * ru (Nickolay Shmyrev) + +============== +Evince 0.2.0 +============== + +Code changes + + * Use poppler glib api + +Interface improvements + + * Rewrite most of the backend code to use threads and make + the interface more responsive + +New features + + * Use page labels as page numbers (Jonathan) + +============== +Evince 0.1.9 +============== + + * Fix a crash in the caja thumbnailer (Fernando Herrera) + +============== +Evince 0.1.8 +============== + +BugFixes + + * Fix crash when opening pdf documents with only 1 page + +============== +Evince 0.1.7 +============== + +New features + + * Caja thumbnailer for pdf (Fernando Herrera) + +BugFixes + + * Make code C89 compliant (Jens Granseuer) + * Fix some issues in the caja thumbnailer (Marco) + * Do not read ps defaults from ggv prefs! (Marco) + +============== +Evince 0.1.6 +============== + +Code changes + + * Use poppler instead of our own xpdf fork (Kristian HĆøgsberg) + +Interface improvements + + * Use an entry for the page control instead of + spin buttons (Marco) + * Expand the sidebar selection widget (Carlos) + +Bug fixes + + * Fix crash in links loading (Jonathan) + * Fix crashes when reloading document (Marco) + +============== +Evince 0.1.5 +============== + +New features + + * Reload menu (Kai Willadsen) + +Interface improvements + + * Support for DnD of files (Carlos Garcia Campos) + * Set the "Previous" and "Next" toolbar + buttons as important (Vincent Noel) + * Make the "best fit" and "fit width" + values act as toggle buttons (Jonathan) + * Autohide the fullscreen button (Kristian HĆøgsberg) + * Added ellipsis to Print item (Bryan) + * Change the fullscreen toolbar to always be in + the popup window (Jonathan) + * Improved sidebar widget (Carlos Garcia Campos) + * Minimal size for the sidebar (Marco) + * Hide the sidebar if the type doesn't + support thumbnailing and indexing (Jonathan) + * Remember the sidebar size (Carlos Garcia Campos) + +Bug fixes + + * Fix compilation on non-gcc platforms (Marco) + * Fix thumbnails generation (Crispin Flowerday) + * Fix best fit size allocation (Martin) + * Replace newlines in the title by spaces (Martin) + * Get rid of the black flash when resizing the window (Jonathan) + * Sync selection with zoom levels (Marco) + * Fix clipboard operations (Marco) + * Fix a crash when the ps document cannot be opened (Marco) + * Fix postcript page selection (Marco) + * Fix page sizing to be idempotent (Marco) + * Fix page sizing toggle buttons (Marco) + * Fix freezing on document with unrecognized links (Marco) + * Check ghostscript minimal version (Marco) + * Fix rendering of landscape documents (Marco) + * Unescape filenames for display (Marco) + +============== +Evince 0.1.4 +============== + +Code changes + + * Add debugging/logging helpers (Marco) + * Add warnings about unimplemented/unknown link + types (Marco) + +Interface improvements + + * Epiphany like fullscreen mode (Christian Persch) + * Save chromes state between sessions (Christian Persch) + * Improve toolbar layout and icons (Luca Ferretti) + +Bug fixes + + * Really fix postscript rendering (Marco) + * Do not leak gs processes on exit (Marco) + * Fix a buffer overflow fix (Marco) + +============== +Evince 0.1.3 +============== + +Code changes + + * Beginnings of continuous viewing (Jonathan) + +New features + + * Support eps (Marco) + * Support gzipped ps (Marco) + +Bug fixes + + * Fix a crash with the postscript view (Marco) + * Fix image view (Jeff Muizelaar) + * Fix a warning when opening documents without + thumbnails (Martin) + * Fix crashes when searched word is not found (Marco) + * Fix a crash when changing page with keys (Marco) + +============== +Evince 0.1.2 +============== + +New features + + * Make search really work (Marco) + +Interface improvements + + * Show password state in the view (Jonathan) + * add GTK_SHADOW_IN to the scrolled window (Martin) + * make sure the selected thumbnail stays visible (Martin) + * Remove history buttons from the toolbar (Marco) + +Bug fixes + + * Fix CID fonts with freetype 2.1.9 (Marco) + * Fix inverted up/down buttons (Marco) + +============== +Evince 0.1.1 +============== + +New features + + * Actually print PDF files + * Navigation history + * Support Links + * Support encrypted PDFs + * A desktop file + +Interface improvements + + * Rename Bookmarks sidebar to Index + * Use filename as title if the file doesn't have a title (Jeff + Muizelaar) or if the title is empty + * Make navigation with thumbnails possible (Anders Carlsson) + * (De)Sensitize actions as necessary (Dave Malcolm) + * Show a wait cursor while the page is rendering + * Center the page; draw a page border + * Undo selection on page change + * Shorter toolbar item labels + * Make selection work in any direction + +Code changes + + * Render PDFs directly as packed RGB + * Update egg-recent code (not used yet) + * Update to latest Xpdf patchlevel + +Bug fixes + + * PostScript backend fixes + * Pixbuf backend fixes (Jeff Muizelaar) + * Set page spin button limits correctly (Anders Carlsson) + * Fix problem when base fonts are installed as pcf files. + * Keep search selection highlighting correct even when + zooming/changing window size (Stephane LOEUILLET) + +Translation updates + + * cs (Miloslav Trmac) + * de (Frank Arnold) + * en_CA (Adam Weinberger) + * es (Antonio Ognio) + * nb (Kjartan Maraas) + * nl (Tino Meinen) + +evince 0.1.0 +------------ + +Initial release. diff --git a/README b/README new file mode 100644 index 00000000..88a44a24 --- /dev/null +++ b/README @@ -0,0 +1,21 @@ +Evince +================================================== +Evince is a document viewer capable of displaying +multiple and single page document formats like PDF +and Postscript. For more general information about +Evince please visit our website at + +http://www.mate.org/projects/evince/ + +This software is licensed under the GPL + +Evince Requirements +================================================== +MATE Platform libraries [ http://www.mate.org/start/ ] +Poppler for PDF viewing [ http://poppler.freedesktop.org/ ] +GhostScript for Postscript viewing [ http://www.cs.wisc.edu/~ghost/ ] + +Evince Optional Backend Libraries +================================================== +DjVuLibre for DjVu viewing [ http://djvulibre.djvuzone.org/ ] +Rar for viewing CBR comics [ http://www.rarsoft.com/ ] diff --git a/TODO b/TODO new file mode 100644 index 00000000..715d57b3 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +See our bugzilla for list of entries we are working at + +http://bugzilla.mate.org/buglist.cgi?product=evince&bug_status=NEW \ No newline at end of file diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..eb12863a --- /dev/null +++ b/autogen.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# Run this to generate all the initial makefiles, etc. + +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. + +PKG_NAME="evince" + +(test -f $srcdir/configure.ac) || { + echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" + echo " top-level $PKG_NAME directory" + exit 1 +} + +which mate-autogen.sh || { + echo "You need to install mate-common from the MATE CVS" + exit 1 +} + +REQUIRED_AUTOMAKE_VERSION=1.10 +REQUIRED_GTK_DOC_VERSION=1.13 +USE_MATE2_MACROS=1 +. mate-autogen.sh diff --git a/backend/Makefile.am b/backend/Makefile.am new file mode 100644 index 00000000..a6dc6e58 --- /dev/null +++ b/backend/Makefile.am @@ -0,0 +1,40 @@ +SUBDIRS = + +# Backends + +if ENABLE_PDF +SUBDIRS += pdf +endif + +if ENABLE_PS +SUBDIRS += ps +endif + +if ENABLE_PIXBUF +SUBDIRS += pixbuf +endif + +if ENABLE_DJVU +SUBDIRS += djvu +endif + +if ENABLE_TIFF +SUBDIRS += tiff +endif + +if ENABLE_DVI +SUBDIRS += dvi +endif + +if ENABLE_COMICS + SUBDIRS += comics +endif + +if ENABLE_IMPRESS + SUBDIRS += impress +endif + +EXTRA_DIST = \ + backend.symbols + +-include $(top_srcdir)/git.mk diff --git a/backend/backend.symbols b/backend/backend.symbols new file mode 100644 index 00000000..6c41cf8f --- /dev/null +++ b/backend/backend.symbols @@ -0,0 +1 @@ +register_evince_backend diff --git a/backend/comics/Makefile.am b/backend/comics/Makefile.am new file mode 100644 index 00000000..0b06ecbc --- /dev/null +++ b/backend/comics/Makefile.am @@ -0,0 +1,32 @@ +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/libdocument \ + -DMATELOCALEDIR=\"$(datadir)/locale\" \ + -DEVINCE_COMPILATION \ + $(BACKEND_CFLAGS) \ + $(LIB_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED) + +backend_LTLIBRARIES = libcomicsdocument.la + +libcomicsdocument_la_SOURCES = \ + comics-document.c \ + comics-document.h + +libcomicsdocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS) +libcomicsdocument_la_LIBADD = \ + $(top_builddir)/libdocument/libevdocument.la \ + $(BACKEND_LIBS) \ + $(LIB_LIBS) + +backend_in_files = comicsdocument.evince-backend.in +backend_DATA = $(backend_in_files:.evince-backend.in=.evince-backend) + +EXTRA_DIST = $(backend_in_files) + +CLEANFILES = $(backend_DATA) + +@EV_INTLTOOL_EVINCE_BACKEND_RULE@ + +-include $(top_srcdir)/git.mk diff --git a/backend/comics/comics-document.c b/backend/comics/comics-document.c new file mode 100644 index 00000000..4d74385a --- /dev/null +++ b/backend/comics/comics-document.c @@ -0,0 +1,936 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* + * Copyright (C) 2009-2010 Juanjo MarĆ­n + * Copyright (C) 2005, Teemu Tervo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef G_OS_WIN32 +# define WIFEXITED(x) ((x) != 3) +# define WEXITSTATUS(x) (x) +#else +# include +#endif + +#include "comics-document.h" +#include "ev-document-misc.h" +#include "ev-document-thumbnails.h" +#include "ev-file-helpers.h" + +#ifdef G_OS_WIN32 +/* On windows g_spawn_command_line_sync reads stdout in O_BINARY mode, not in O_TEXT mode. + * As a consequence, newlines are in a platform dependent representation (\r\n). This + * might be considered a bug in glib. + */ +#define EV_EOL "\r\n" +#else +#define EV_EOL "\n" +#endif + +typedef enum +{ + RARLABS, + GNAUNRAR, + UNZIP, + P7ZIP, + TAR +} ComicBookDecompressType; + +typedef struct _ComicsDocumentClass ComicsDocumentClass; + +struct _ComicsDocumentClass +{ + EvDocumentClass parent_class; +}; + +struct _ComicsDocument +{ + EvDocument parent_instance; + + gchar *archive, *dir; + GPtrArray *page_names; + gchar *selected_command, *alternative_command; + gchar *extract_command, *list_command, *decompress_tmp; + gboolean regex_arg; + gint offset; + ComicBookDecompressType command_usage; +}; + +#define OFFSET_7Z 53 +#define OFFSET_ZIP 2 +#define NO_OFFSET 0 + +/* For perfomance reasons of 7z* we've choosen to decompress on the temporary + * directory instead of decompressing on the stdout */ + +/** + * @extract: command line arguments to pass to extract a file from the archive + * to stdout. + * @list: command line arguments to list the archive contents + * @decompress_tmp: command line arguments to pass to extract the archive + * into a directory. + * @regex_arg: whether the command can accept regex expressions + * @offset: the position offset of the filename on each line in the output of + * running the @list command + */ +typedef struct { + char *extract; + char *list; + char *decompress_tmp; + gboolean regex_arg; + gint offset; +} ComicBookDecompressCommand; + +static const ComicBookDecompressCommand command_usage_def[] = { + /* RARLABS unrar */ + {"%s p -c- -ierr --", "%s vb -c- -- %s", NULL , FALSE, NO_OFFSET}, + + /* GNA! unrar */ + {NULL , "%s t %s" , "%s -xf %s %s" , FALSE, NO_OFFSET}, + + /* unzip */ + {"%s -p -C --" , "%s %s" , NULL , TRUE , OFFSET_ZIP}, + + /* 7zip */ + {NULL , "%s l -- %s" , "%s x -y %s -o%s", FALSE, OFFSET_7Z}, + + /* tar */ + {"%s -xOf" , "%s -tf %s" , NULL , FALSE, NO_OFFSET} +}; + +static void comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); + +static GSList* get_supported_image_extensions (void); +static void get_page_size_area_prepared_cb (GdkPixbufLoader *loader, + gpointer data); +static void render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader, + gint width, + gint height, + gpointer data); +static char** extract_argv (EvDocument *document, + gint page); + + +EV_BACKEND_REGISTER_WITH_CODE (ComicsDocument, comics_document, + { + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, + comics_document_document_thumbnails_iface_init); + } ); + +/** + * comics_regex_quote: + * @unquoted_string: a literal string + * + * Quotes a string so unzip will not interpret the regex expressions of + * @unquoted_string. Basically, this functions uses [] to disable regex + * expressions. The return value must be freed with * g_free() + * + * Return value: quoted and disabled-regex string + **/ +static gchar * +comics_regex_quote (const gchar *unquoted_string) +{ + const gchar *p; + GString *dest; + + dest = g_string_new ("'"); + + p = unquoted_string; + + while (*p) { + switch (*p) { + /* * matches a sequence of 0 or more characters */ + case ('*'): + /* ? matches exactly 1 charactere */ + case ('?'): + /* [...] matches any single character found inside + * the brackets. Disabling the first bracket is enough. + */ + case ('['): + g_string_append (dest, "["); + g_string_append_c (dest, *p); + g_string_append (dest, "]"); + break; + /* Because \ escapes regex expressions that we are + * disabling for unzip, we need to disable \ too */ + case ('\\'): + g_string_append (dest, "[\\\\]"); + break; + /* Escape single quote inside the string */ + case ('\''): + g_string_append (dest, "'\\''"); + break; + default: + g_string_append_c (dest, *p); + break; + } + ++p; + } + g_string_append_c (dest, '\''); + return g_string_free (dest, FALSE); +} + + +/* This function manages the command for decompressing a comic book */ +static gboolean +comics_decompress_temp_dir (const gchar *command_decompress_tmp, + const gchar *command, + GError **error) +{ + gboolean success; + gchar *std_out, *basename; + GError *err = NULL; + gint retval; + + success = g_spawn_command_line_sync (command_decompress_tmp, &std_out, + NULL, &retval, &err); + basename = g_path_get_basename (command); + if (!success) { + g_set_error (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("Error launching the command ā€œ%sā€ in order to " + "decompress the comic book: %s"), + basename, + err->message); + g_error_free (err); + } else if (WIFEXITED (retval)) { + if (WEXITSTATUS (retval) == EXIT_SUCCESS) { + g_free (std_out); + g_free (basename); + return TRUE; + } else { + g_set_error (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("The command ā€œ%sā€ failed at " + "decompressing the comic book."), + basename); + g_free (std_out); + } + } else { + g_set_error (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("The command ā€œ%sā€ did not end normally."), + basename); + g_free (std_out); + } + g_free (basename); + return FALSE; +} + +/* This function shows how to use the choosen command for decompressing a + * comic book file. It modifies fields of the ComicsDocument struct with + * this information */ +static gboolean +comics_generate_command_lines (ComicsDocument *comics_document, + GError **error) +{ + gchar *quoted_file, *quoted_file_aux; + gchar *quoted_command; + ComicBookDecompressType type; + + type = comics_document->command_usage; + comics_document->regex_arg = command_usage_def[type].regex_arg; + quoted_command = g_shell_quote (comics_document->selected_command); + if (comics_document->regex_arg) { + quoted_file = comics_regex_quote (comics_document->archive); + quoted_file_aux = g_shell_quote (comics_document->archive); + comics_document->list_command = + g_strdup_printf (command_usage_def[type].list, + comics_document->alternative_command, + quoted_file_aux); + g_free (quoted_file_aux); + } else { + quoted_file = g_shell_quote (comics_document->archive); + comics_document->list_command = + g_strdup_printf (command_usage_def[type].list, + quoted_command, quoted_file); + } + comics_document->extract_command = + g_strdup_printf (command_usage_def[type].extract, + quoted_command); + comics_document->offset = command_usage_def[type].offset; + if (command_usage_def[type].decompress_tmp) { + comics_document->dir = ev_mkdtemp ("evince-comics-XXXXXX", error); + if (comics_document->dir == NULL) + return FALSE; + + /* unrar-free can't create directories, but ev_mkdtemp already created the dir */ + + comics_document->decompress_tmp = + g_strdup_printf (command_usage_def[type].decompress_tmp, + quoted_command, quoted_file, + comics_document->dir); + g_free (quoted_file); + g_free (quoted_command); + + if (!comics_decompress_temp_dir (comics_document->decompress_tmp, + comics_document->selected_command, error)) + return FALSE; + else + return TRUE; + } else { + g_free (quoted_file); + g_free (quoted_command); + return TRUE; + } + +} + +/* This function chooses an external command for decompressing a comic + * book based on its mime tipe. */ +static gboolean +comics_check_decompress_command (gchar *mime_type, + ComicsDocument *comics_document, + GError **error) +{ + gboolean success; + gchar *std_out, *std_err; + gint retval; + GError *err = NULL; + + /* FIXME, use proper cbr/cbz mime types once they're + * included in shared-mime-info */ + + if (!strcmp (mime_type, "application/x-cbr") || + !strcmp (mime_type, "application/x-rar")) { + /* The RARLAB provides a no-charge proprietary (freeware) + * decompress-only client for Linux called unrar. Another + * option is a GPLv2-licensed command-line tool developed by + * the Gna! project. Confusingly enough, the free software RAR + * decoder is also named unrar. For this reason we need to add + * some lines for disambiguation. Sorry for the added the + * complexity but it's life :) + * Finally, some distributions, like Debian, rename this free + * option as unrar-free. + * */ + comics_document->selected_command = + g_find_program_in_path ("unrar"); + if (comics_document->selected_command) { + /* We only use std_err to avoid printing useless error + * messages on the terminal */ + success = + g_spawn_command_line_sync ( + comics_document->selected_command, + &std_out, &std_err, + &retval, &err); + if (!success) { + g_propagate_error (error, err); + g_error_free (err); + return FALSE; + /* I don't check retval status because RARLAB unrar + * doesn't have a way to return 0 without involving an + * operation with a file*/ + } else if (WIFEXITED (retval)) { + if (g_strrstr (std_out,"freeware") != NULL) + /* The RARLAB freeware client */ + comics_document->command_usage = RARLABS; + else + /* The Gna! free software client */ + comics_document->command_usage = GNAUNRAR; + + g_free (std_out); + g_free (std_err); + return TRUE; + } + } + /* The Gna! free software client with Debian naming convention */ + comics_document->selected_command = + g_find_program_in_path ("unrar-free"); + if (comics_document->selected_command) { + comics_document->command_usage = GNAUNRAR; + return TRUE; + } + + } else if (!strcmp (mime_type, "application/x-cbz") || + !strcmp (mime_type, "application/zip")) { + /* InfoZIP's unzip program */ + comics_document->selected_command = + g_find_program_in_path ("unzip"); + comics_document->alternative_command = + g_find_program_in_path ("zipnote"); + if (comics_document->selected_command && + comics_document->alternative_command) { + comics_document->command_usage = UNZIP; + return TRUE; + } + + } else if (!strcmp (mime_type, "application/x-cb7") || + !strcmp (mime_type, "application/x-7z-compressed")) { + /* 7zr, 7za and 7z are the commands from the p7zip project able + * to decompress .7z files */ + comics_document->selected_command = + g_find_program_in_path ("7zr"); + if (comics_document->selected_command) { + comics_document->command_usage = P7ZIP; + return TRUE; + } + comics_document->selected_command = + g_find_program_in_path ("7za"); + if (comics_document->selected_command) { + comics_document->command_usage = P7ZIP; + return TRUE; + } + comics_document->selected_command = + g_find_program_in_path ("7z"); + if (comics_document->selected_command) { + comics_document->command_usage = P7ZIP; + return TRUE; + } + } else if (!strcmp (mime_type, "application/x-cbt") || + !strcmp (mime_type, "application/x-tar")) { + /* tar utility (Tape ARchive) */ + comics_document->selected_command = + g_find_program_in_path ("tar"); + if (comics_document->selected_command) { + comics_document->command_usage = TAR; + return TRUE; + } + } else { + g_set_error (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("Not a comic book MIME type: %s"), + mime_type); + return FALSE; + } + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("Can't find an appropriate command to " + "decompress this type of comic book")); + return FALSE; +} + +static int +sort_page_names (gconstpointer a, + gconstpointer b) +{ + return strcmp (* (const char **) a, * (const char **) b); +} + +static gboolean +comics_document_load (EvDocument *document, + const char *uri, + GError **error) +{ + ComicsDocument *comics_document = COMICS_DOCUMENT (document); + GSList *supported_extensions; + gchar *std_out; + gchar *mime_type; + gchar **cb_files, *cb_file; + gboolean success; + int i, retval; + GError *err = NULL; + + comics_document->archive = g_filename_from_uri (uri, NULL, error); + if (!comics_document->archive) + return FALSE; + + mime_type = ev_file_get_mime_type (uri, FALSE, &err); + if (!mime_type) { + if (err) { + g_propagate_error (error, err); + } else { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("Unknown MIME Type")); + } + + return FALSE; + } + + if (!comics_check_decompress_command (mime_type, comics_document, + error)) { + g_free (mime_type); + return FALSE; + } else if (!comics_generate_command_lines (comics_document, error)) { + g_free (mime_type); + return FALSE; + } + + g_free (mime_type); + + /* Get list of files in archive */ + success = g_spawn_command_line_sync (comics_document->list_command, + &std_out, NULL, &retval, error); + + if (!success) { + return FALSE; + } else if (!WIFEXITED(retval) || WEXITSTATUS(retval) != EXIT_SUCCESS) { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("File corrupted")); + return FALSE; + } + + /* FIXME: is this safe against filenames containing \n in the archive ? */ + cb_files = g_strsplit (std_out, EV_EOL, 0); + + g_free (std_out); + + if (!cb_files) { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("No files in archive")); + return FALSE; + } + + comics_document->page_names = g_ptr_array_sized_new (64); + + supported_extensions = get_supported_image_extensions (); + for (i = 0; cb_files[i] != NULL; i++) { + if (comics_document->offset != NO_OFFSET) { + if (g_utf8_strlen (cb_files[i],-1) > + comics_document->offset) { + cb_file = + g_utf8_offset_to_pointer (cb_files[i], + comics_document->offset); + } else { + continue; + } + } else { + cb_file = cb_files[i]; + } + gchar *suffix = g_strrstr (cb_file, "."); + if (!suffix) + continue; + suffix = g_ascii_strdown (suffix + 1, -1); + if (g_slist_find_custom (supported_extensions, suffix, + (GCompareFunc) strcmp) != NULL) { + g_ptr_array_add (comics_document->page_names, + g_strstrip (g_strdup (cb_file))); + } + g_free (suffix); + } + g_strfreev (cb_files); + g_slist_foreach (supported_extensions, (GFunc) g_free, NULL); + g_slist_free (supported_extensions); + + if (comics_document->page_names->len == 0) { + g_set_error (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("No images found in archive %s"), + uri); + return FALSE; + } + + /* Now sort the pages */ + g_ptr_array_sort (comics_document->page_names, sort_page_names); + + return TRUE; +} + + +static gboolean +comics_document_save (EvDocument *document, + const char *uri, + GError **error) +{ + ComicsDocument *comics_document = COMICS_DOCUMENT (document); + + return ev_xfer_uri_simple (comics_document->archive, uri, error); +} + +static int +comics_document_get_n_pages (EvDocument *document) +{ + ComicsDocument *comics_document = COMICS_DOCUMENT (document); + + if (comics_document->page_names == NULL) + return 0; + + return comics_document->page_names->len; +} + +static void +comics_document_get_page_size (EvDocument *document, + EvPage *page, + double *width, + double *height) +{ + GdkPixbufLoader *loader; + char **argv; + guchar buf[1024]; + gboolean success, got_size = FALSE; + gint outpipe = -1; + GPid child_pid; + gssize bytes; + GdkPixbuf *pixbuf; + gchar *filename; + ComicsDocument *comics_document = COMICS_DOCUMENT (document); + + if (!comics_document->decompress_tmp) { + argv = extract_argv (document, page->index); + success = g_spawn_async_with_pipes (NULL, argv, NULL, + G_SPAWN_SEARCH_PATH | + G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, + &child_pid, + NULL, &outpipe, NULL, NULL); + g_strfreev (argv); + g_return_if_fail (success == TRUE); + + loader = gdk_pixbuf_loader_new (); + g_signal_connect (loader, "area-prepared", + G_CALLBACK (get_page_size_area_prepared_cb), + &got_size); + + while (outpipe >= 0) { + bytes = read (outpipe, buf, 1024); + + if (bytes > 0) + gdk_pixbuf_loader_write (loader, buf, bytes, NULL); + if (bytes <= 0 || got_size) { + close (outpipe); + outpipe = -1; + gdk_pixbuf_loader_close (loader, NULL); + } + } + + if (gdk_pixbuf_loader_get_pixbuf (loader)) { + pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); + if (width) + *width = gdk_pixbuf_get_width (pixbuf); + if (height) + *height = gdk_pixbuf_get_height (pixbuf); + } + + g_spawn_close_pid (child_pid); + g_object_unref (loader); + } else { + filename = g_build_filename (comics_document->dir, + (char *) comics_document->page_names->pdata[page->index], + NULL); + pixbuf = gdk_pixbuf_new_from_file (filename, NULL); + g_free (filename); + if (width) + *width = gdk_pixbuf_get_width (pixbuf); + if (height) + *height = gdk_pixbuf_get_height (pixbuf); + } +} + +static void +get_page_size_area_prepared_cb (GdkPixbufLoader *loader, + gpointer data) +{ + gboolean *got_size = data; + *got_size = TRUE; +} + +static GdkPixbuf * +comics_document_render_pixbuf (EvDocument *document, + EvRenderContext *rc) +{ + GdkPixbufLoader *loader; + GdkPixbuf *rotated_pixbuf; + char **argv; + guchar buf[4096]; + gboolean success; + gint outpipe = -1; + GPid child_pid; + gssize bytes; + gint width, height; + gchar *filename; + ComicsDocument *comics_document = COMICS_DOCUMENT (document); + + if (!comics_document->decompress_tmp) { + argv = extract_argv (document, rc->page->index); + success = g_spawn_async_with_pipes (NULL, argv, NULL, + G_SPAWN_SEARCH_PATH | + G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, + &child_pid, + NULL, &outpipe, NULL, NULL); + g_strfreev (argv); + g_return_val_if_fail (success == TRUE, NULL); + + loader = gdk_pixbuf_loader_new (); + g_signal_connect (loader, "size-prepared", + G_CALLBACK (render_pixbuf_size_prepared_cb), + &rc->scale); + + while (outpipe >= 0) { + bytes = read (outpipe, buf, 4096); + + if (bytes > 0) { + gdk_pixbuf_loader_write (loader, buf, bytes, + NULL); + } else if (bytes <= 0) { + close (outpipe); + gdk_pixbuf_loader_close (loader, NULL); + outpipe = -1; + } + } + + rotated_pixbuf = gdk_pixbuf_rotate_simple ( + gdk_pixbuf_loader_get_pixbuf (loader), + 360 - rc->rotation); + g_spawn_close_pid (child_pid); + g_object_unref (loader); + } else { + filename = + g_build_filename (comics_document->dir, + (char *) comics_document->page_names->pdata[rc->page->index], + NULL); + + gdk_pixbuf_get_file_info (filename, &width, &height); + + rotated_pixbuf = + gdk_pixbuf_rotate_simple (gdk_pixbuf_new_from_file_at_size ( + filename, width * (rc->scale) + 0.5, + height * (rc->scale) + 0.5, NULL), + 360 - rc->rotation); + g_free (filename); + + } + return rotated_pixbuf; +} + +static cairo_surface_t * +comics_document_render (EvDocument *document, + EvRenderContext *rc) +{ + GdkPixbuf *pixbuf; + cairo_surface_t *surface; + + pixbuf = comics_document_render_pixbuf (document, rc); + surface = ev_document_misc_surface_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + + return surface; +} + +static void +render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader, + gint width, + gint height, + gpointer data) +{ + double *scale = data; + int w = (width * (*scale) + 0.5); + int h = (height * (*scale) + 0.5); + + gdk_pixbuf_loader_set_size (loader, w, h); +} + +/** + * comics_remove_dir: Removes a directory recursively. + * Returns: + * 0 if it was successfully deleted, + * -1 if an error occurred + */ +static int +comics_remove_dir (gchar *path_name) +{ + GDir *content_dir; + const gchar *filename; + gchar *filename_with_path; + + if (g_file_test (path_name, G_FILE_TEST_IS_DIR)) { + content_dir = g_dir_open (path_name, 0, NULL); + filename = g_dir_read_name (content_dir); + while (filename) { + filename_with_path = + g_build_filename (path_name, + filename, NULL); + comics_remove_dir (filename_with_path); + g_free (filename_with_path); + filename = g_dir_read_name (content_dir); + } + g_dir_close (content_dir); + } + /* Note from g_remove() documentation: on Windows, it is in general not + * possible to remove a file that is open to some process, or mapped + * into memory.*/ + return (g_remove (path_name)); +} + +static void +comics_document_finalize (GObject *object) +{ + ComicsDocument *comics_document = COMICS_DOCUMENT (object); + + if (comics_document->decompress_tmp) { + if (comics_remove_dir (comics_document->dir) == -1) + g_warning (_("There was an error deleting ā€œ%sā€."), + comics_document->dir); + g_free (comics_document->dir); + } + + if (comics_document->page_names) { + g_ptr_array_foreach (comics_document->page_names, (GFunc) g_free, NULL); + g_ptr_array_free (comics_document->page_names, TRUE); + } + + g_free (comics_document->archive); + g_free (comics_document->selected_command); + g_free (comics_document->alternative_command); + g_free (comics_document->extract_command); + g_free (comics_document->list_command); + + G_OBJECT_CLASS (comics_document_parent_class)->finalize (object); +} + +static void +comics_document_class_init (ComicsDocumentClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass); + + gobject_class->finalize = comics_document_finalize; + + ev_document_class->load = comics_document_load; + ev_document_class->save = comics_document_save; + ev_document_class->get_n_pages = comics_document_get_n_pages; + ev_document_class->get_page_size = comics_document_get_page_size; + ev_document_class->render = comics_document_render; +} + +static void +comics_document_init (ComicsDocument *comics_document) +{ + comics_document->archive = NULL; + comics_document->page_names = NULL; + comics_document->extract_command = NULL; +} + +/* Returns a list of file extensions supported by gdk-pixbuf */ +static GSList* +get_supported_image_extensions() +{ + GSList *extensions = NULL; + GSList *formats = gdk_pixbuf_get_formats (); + GSList *l; + + for (l = formats; l != NULL; l = l->next) { + int i; + gchar **ext = gdk_pixbuf_format_get_extensions (l->data); + + for (i = 0; ext[i] != NULL; i++) { + extensions = g_slist_append (extensions, + g_strdup (ext[i])); + } + + g_strfreev (ext); + } + + g_slist_free (formats); + return extensions; +} + +static GdkPixbuf * +comics_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document, + EvRenderContext *rc, + gboolean border) +{ + GdkPixbuf *thumbnail; + + thumbnail = comics_document_render_pixbuf (EV_DOCUMENT (document), rc); + + if (border) { + GdkPixbuf *tmp_pixbuf = thumbnail; + + thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf); + g_object_unref (tmp_pixbuf); + } + + return thumbnail; +} + +static void +comics_document_thumbnails_get_dimensions (EvDocumentThumbnails *document, + EvRenderContext *rc, + gint *width, + gint *height) +{ + gdouble page_width, page_height; + + comics_document_get_page_size (EV_DOCUMENT (document), rc->page, + &page_width, &page_height); + + if (rc->rotation == 90 || rc->rotation == 270) { + *width = (gint) (page_height * rc->scale); + *height = (gint) (page_width * rc->scale); + } else { + *width = (gint) (page_width * rc->scale); + *height = (gint) (page_height * rc->scale); + } +} + +static void +comics_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface) +{ + iface->get_thumbnail = comics_document_thumbnails_get_thumbnail; + iface->get_dimensions = comics_document_thumbnails_get_dimensions; +} + +static char** +extract_argv (EvDocument *document, gint page) +{ + ComicsDocument *comics_document = COMICS_DOCUMENT (document); + char **argv; + char *command_line, *quoted_archive, *quoted_filename; + GError *err = NULL; + + if (page >= comics_document->page_names->len) + return NULL; + + if (comics_document->regex_arg) { + quoted_archive = comics_regex_quote (comics_document->archive); + quoted_filename = + comics_regex_quote (comics_document->page_names->pdata[page]); + } else { + quoted_archive = g_shell_quote (comics_document->archive); + quoted_filename = g_shell_quote (comics_document->page_names->pdata[page]); + } + + command_line = g_strdup_printf ("%s %s %s", + comics_document->extract_command, + quoted_archive, + quoted_filename); + g_shell_parse_argv (command_line, NULL, &argv, &err); + + if (err) { + g_warning (_("Error %s"), err->message); + g_error_free (err); + return NULL; + } + + g_free (command_line); + g_free (quoted_archive); + g_free (quoted_filename); + return argv; +} diff --git a/backend/comics/comics-document.h b/backend/comics/comics-document.h new file mode 100644 index 00000000..76feef05 --- /dev/null +++ b/backend/comics/comics-document.h @@ -0,0 +1,38 @@ +/* comics-document.h: Implementation of EvDocument for comic book archives + * Copyright (C) 2005, Teemu Tervo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __COMICS_DOCUMENT_H__ +#define __COMICS_DOCUMENT_H__ + +#include "ev-document.h" + +G_BEGIN_DECLS + +#define COMICS_TYPE_DOCUMENT (comics_document_get_type ()) +#define COMICS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), COMICS_TYPE_DOCUMENT, ComicsDocument)) +#define COMICS_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), COMICS_TYPE_DOCUMENT)) + +typedef struct _ComicsDocument ComicsDocument; + +GType comics_document_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT GType register_evince_backend (GTypeModule *module); + +G_END_DECLS + +#endif /* __COMICS_DOCUMENT_H__ */ diff --git a/backend/comics/comicsdocument.evince-backend.in b/backend/comics/comicsdocument.evince-backend.in new file mode 100644 index 00000000..90d4c01b --- /dev/null +++ b/backend/comics/comicsdocument.evince-backend.in @@ -0,0 +1,4 @@ +[Evince Backend] +Module=comicsdocument +_TypeDescription=Comic Books +MimeType=application/x-cbr;application/x-cbz;application/x-cb7;application/x-cbt; diff --git a/backend/djvu/Makefile.am b/backend/djvu/Makefile.am new file mode 100644 index 00000000..6bc9d06e --- /dev/null +++ b/backend/djvu/Makefile.am @@ -0,0 +1,38 @@ +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/libdocument \ + -DMATEICONDIR=\""${prefix}/${DATADIRNAME}/pixmaps"\" \ + -DMATELOCALEDIR=\"$(datadir)/locale\" \ + -DEVINCE_COMPILATION \ + $(BACKEND_CFLAGS) \ + $(DJVU_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED) + +backend_LTLIBRARIES = libdjvudocument.la + +libdjvudocument_la_SOURCES = \ + djvu-document.c \ + djvu-document.h \ + djvu-document-private.h \ + djvu-links.c \ + djvu-links.h \ + djvu-text-page.c \ + djvu-text-page.h + +libdjvudocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS) +libdjvudocument_la_LIBADD = \ + $(top_builddir)/libdocument/libevdocument.la \ + $(BACKEND_LIBS) \ + $(DJVU_LIBS) + +backend_in_files = djvudocument.evince-backend.in +backend_DATA = $(backend_in_files:.evince-backend.in=.evince-backend) + +EXTRA_DIST = $(backend_in_files) + +CLEANFILES = $(backend_DATA) + +@EV_INTLTOOL_EVINCE_BACKEND_RULE@ + +-include $(top_srcdir)/git.mk diff --git a/backend/djvu/djvu-document-private.h b/backend/djvu/djvu-document-private.h new file mode 100644 index 00000000..2ec9b67a --- /dev/null +++ b/backend/djvu/djvu-document-private.h @@ -0,0 +1,48 @@ +/* + * Declarations used throughout the djvu classes + * + * Copyright (C) 2006, Michael Hofmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __DJVU_DOCUMENT_INTERNAL_H__ +#define __DJVU_DOCUMENT_INTERNAL_H__ + +#include "djvu-document.h" + +#include + +struct _DjvuDocument { + EvDocument parent_instance; + + ddjvu_context_t *d_context; + ddjvu_document_t *d_document; + ddjvu_format_t *d_format; + ddjvu_format_t *thumbs_format; + + gchar *uri; + + /* PS exporter */ + gchar *ps_filename; + GString *opts; +}; + +int djvu_document_get_n_pages (EvDocument *document); +void djvu_handle_events (DjvuDocument *djvu_document, + int wait, + GError **error); + +#endif /* __DJVU_DOCUMENT_INTERNAL_H__ */ diff --git a/backend/djvu/djvu-document.c b/backend/djvu/djvu-document.c new file mode 100644 index 00000000..aa0e595d --- /dev/null +++ b/backend/djvu/djvu-document.c @@ -0,0 +1,706 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* + * Copyright (C) 2005, Nickolay V. Shmyrev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include +#include "djvu-document.h" +#include "djvu-text-page.h" +#include "djvu-links.h" +#include "djvu-document-private.h" +#include "ev-document-thumbnails.h" +#include "ev-file-exporter.h" +#include "ev-document-misc.h" +#include "ev-document-find.h" +#include "ev-document-links.h" +#include "ev-selection.h" +#include "ev-file-helpers.h" + +#include +#include +#include +#include + +#define SCALE_FACTOR 0.2 + +enum { + PROP_0, + PROP_TITLE +}; + +struct _DjvuDocumentClass +{ + EvDocumentClass parent_class; +}; + +typedef struct _DjvuDocumentClass DjvuDocumentClass; + +static void djvu_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); +static void djvu_document_file_exporter_iface_init (EvFileExporterInterface *iface); +static void djvu_document_find_iface_init (EvDocumentFindInterface *iface); +static void djvu_document_document_links_iface_init (EvDocumentLinksInterface *iface); +static void djvu_selection_iface_init (EvSelectionInterface *iface); + +EV_BACKEND_REGISTER_WITH_CODE (DjvuDocument, djvu_document, + { + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, djvu_document_document_thumbnails_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_FILE_EXPORTER, djvu_document_file_exporter_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_FIND, djvu_document_find_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_LINKS, djvu_document_document_links_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_SELECTION, djvu_selection_iface_init); + }); + + +#define EV_DJVU_ERROR ev_djvu_error_quark () + +static GQuark +ev_djvu_error_quark (void) +{ + static GQuark q = 0; + if (q == 0) + q = g_quark_from_string ("ev-djvu-quark"); + + return q; +} + +static void +handle_message (const ddjvu_message_t *msg, GError **error) +{ + switch (msg->m_any.tag) { + case DDJVU_ERROR: { + gchar *error_str; + + if (msg->m_error.filename) { + error_str = g_strdup_printf ("DjvuLibre error: %s:%d", + msg->m_error.filename, + msg->m_error.lineno); + } else { + error_str = g_strdup_printf ("DjvuLibre error: %s", + msg->m_error.message); + } + + if (error) { + g_set_error_literal (error, EV_DJVU_ERROR, 0, error_str); + } else { + g_warning ("%s", error_str); + } + + g_free (error_str); + return; + } + break; + default: + break; + } +} + +void +djvu_handle_events (DjvuDocument *djvu_document, int wait, GError **error) +{ + ddjvu_context_t *ctx = djvu_document->d_context; + const ddjvu_message_t *msg; + + if (!ctx) + return; + + if (wait) + ddjvu_message_wait (ctx); + + while ((msg = ddjvu_message_peek (ctx))) { + handle_message (msg, error); + ddjvu_message_pop (ctx); + if (error && *error) + return; + } +} + +static void +djvu_wait_for_message (DjvuDocument *djvu_document, ddjvu_message_tag_t message, GError **error) +{ + ddjvu_context_t *ctx = djvu_document->d_context; + const ddjvu_message_t *msg; + + ddjvu_message_wait (ctx); + while ((msg = ddjvu_message_peek (ctx)) && (msg->m_any.tag != message)) { + handle_message (msg, error); + ddjvu_message_pop (ctx); + if (error && *error) + return; + } + if (msg && msg->m_any.tag == message) + ddjvu_message_pop (ctx); +} + +static gboolean +djvu_document_load (EvDocument *document, + const char *uri, + GError **error) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document); + ddjvu_document_t *doc; + gchar *filename; + gboolean missing_files = FALSE; + GError *djvu_error = NULL; + + /* FIXME: We could actually load uris */ + filename = g_filename_from_uri (uri, NULL, error); + if (!filename) + return FALSE; + + doc = ddjvu_document_create_by_filename (djvu_document->d_context, filename, TRUE); + + if (!doc) { + g_free (filename); + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("DjVu document has incorrect format")); + return FALSE; + } + + if (djvu_document->d_document) + ddjvu_document_release (djvu_document->d_document); + + djvu_document->d_document = doc; + + djvu_wait_for_message (djvu_document, DDJVU_DOCINFO, &djvu_error); + if (djvu_error) { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + djvu_error->message); + g_error_free (djvu_error); + g_free (filename); + ddjvu_document_release (djvu_document->d_document); + djvu_document->d_document = NULL; + + return FALSE; + } + + if (ddjvu_document_decoding_error (djvu_document->d_document)) + djvu_handle_events (djvu_document, TRUE, &djvu_error); + + if (djvu_error) { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + djvu_error->message); + g_error_free (djvu_error); + g_free (filename); + ddjvu_document_release (djvu_document->d_document); + djvu_document->d_document = NULL; + + return FALSE; + } + + g_free (djvu_document->uri); + djvu_document->uri = g_strdup (uri); + + if (ddjvu_document_get_type (djvu_document->d_document) == DDJVU_DOCTYPE_INDIRECT) { + gint n_files; + gint i; + gchar *base; + + base = g_path_get_dirname (filename); + + n_files = ddjvu_document_get_filenum (djvu_document->d_document); + for (i = 0; i < n_files; i++) { + struct ddjvu_fileinfo_s fileinfo; + gchar *file; + + ddjvu_document_get_fileinfo (djvu_document->d_document, + i, &fileinfo); + + if (fileinfo.type != 'P') + continue; + + file = g_build_filename (base, fileinfo.id, NULL); + if (!g_file_test (file, G_FILE_TEST_EXISTS)) { + missing_files = TRUE; + g_free (file); + + break; + } + g_free (file); + } + g_free (base); + } + g_free (filename); + + if (missing_files) { + g_set_error_literal (error, + G_FILE_ERROR, + G_FILE_ERROR_EXIST, + _("The document is composed of several files. " + "One or more of these files cannot be accessed.")); + + return FALSE; + } + + return TRUE; +} + + +static gboolean +djvu_document_save (EvDocument *document, + const char *uri, + GError **error) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document); + + return ev_xfer_uri_simple (djvu_document->uri, uri, error); +} + +int +djvu_document_get_n_pages (EvDocument *document) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document); + + g_return_val_if_fail (djvu_document->d_document, 0); + + return ddjvu_document_get_pagenum (djvu_document->d_document); +} + +static void +document_get_page_size (DjvuDocument *djvu_document, + gint page, + double *width, + double *height) +{ + ddjvu_pageinfo_t info; + ddjvu_status_t r; + + while ((r = ddjvu_document_get_pageinfo(djvu_document->d_document, page, &info)) < DDJVU_JOB_OK) + djvu_handle_events(djvu_document, TRUE, NULL); + + if (r >= DDJVU_JOB_FAILED) + djvu_handle_events(djvu_document, TRUE, NULL); + + *width = info.width * SCALE_FACTOR; + *height = info.height * SCALE_FACTOR; +} + +static void +djvu_document_get_page_size (EvDocument *document, + EvPage *page, + double *width, + double *height) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document); + + g_return_if_fail (djvu_document->d_document); + + document_get_page_size (djvu_document, page->index, + width, height); +} + +static cairo_surface_t * +djvu_document_render (EvDocument *document, + EvRenderContext *rc) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document); + cairo_surface_t *surface; + gchar *pixels; + gint rowstride; + ddjvu_rect_t rrect; + ddjvu_rect_t prect; + ddjvu_page_t *d_page; + ddjvu_page_rotation_t rotation; + double page_width, page_height, tmp; + + d_page = ddjvu_page_create_by_pageno (djvu_document->d_document, rc->page->index); + + while (!ddjvu_page_decoding_done (d_page)) + djvu_handle_events(djvu_document, TRUE, NULL); + + page_width = ddjvu_page_get_width (d_page) * rc->scale * SCALE_FACTOR + 0.5; + page_height = ddjvu_page_get_height (d_page) * rc->scale * SCALE_FACTOR + 0.5; + + switch (rc->rotation) { + case 90: + rotation = DDJVU_ROTATE_90; + tmp = page_height; + page_height = page_width; + page_width = tmp; + + break; + case 180: + rotation = DDJVU_ROTATE_180; + + break; + case 270: + rotation = DDJVU_ROTATE_270; + tmp = page_height; + page_height = page_width; + page_width = tmp; + + break; + default: + rotation = DDJVU_ROTATE_0; + } + + surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, + page_width, page_height); + rowstride = cairo_image_surface_get_stride (surface); + pixels = (gchar *)cairo_image_surface_get_data (surface); + + prect.x = 0; + prect.y = 0; + prect.w = page_width; + prect.h = page_height; + rrect = prect; + + ddjvu_page_set_rotation (d_page, rotation); + + ddjvu_page_render (d_page, DDJVU_RENDER_COLOR, + &prect, + &rrect, + djvu_document->d_format, + rowstride, + pixels); + + cairo_surface_mark_dirty (surface); + + return surface; +} + +static void +djvu_document_finalize (GObject *object) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (object); + + if (djvu_document->d_document) + ddjvu_document_release (djvu_document->d_document); + + if (djvu_document->opts) + g_string_free (djvu_document->opts, TRUE); + + if (djvu_document->ps_filename) + g_free (djvu_document->ps_filename); + + ddjvu_context_release (djvu_document->d_context); + ddjvu_format_release (djvu_document->d_format); + ddjvu_format_release (djvu_document->thumbs_format); + g_free (djvu_document->uri); + + G_OBJECT_CLASS (djvu_document_parent_class)->finalize (object); +} + +static void +djvu_document_class_init (DjvuDocumentClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass); + + gobject_class->finalize = djvu_document_finalize; + + ev_document_class->load = djvu_document_load; + ev_document_class->save = djvu_document_save; + ev_document_class->get_n_pages = djvu_document_get_n_pages; + ev_document_class->get_page_size = djvu_document_get_page_size; + ev_document_class->render = djvu_document_render; +} + +static gchar * +djvu_text_copy (DjvuDocument *djvu_document, + gint page, + EvRectangle *rectangle) +{ + miniexp_t page_text; + gchar *text = NULL; + + while ((page_text = + ddjvu_document_get_pagetext (djvu_document->d_document, + page, "char")) == miniexp_dummy) + djvu_handle_events (djvu_document, TRUE, NULL); + + if (page_text != miniexp_nil) { + DjvuTextPage *page = djvu_text_page_new (page_text); + + text = djvu_text_page_copy (page, rectangle); + djvu_text_page_free (page); + ddjvu_miniexp_release (djvu_document->d_document, page_text); + } + + return text; +} + +static gchar * +djvu_selection_get_selected_text (EvSelection *selection, + EvPage *page, + EvSelectionStyle style, + EvRectangle *points) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (selection); + double width, height; + EvRectangle rectangle; + gchar *text; + + djvu_document_get_page_size (EV_DOCUMENT (djvu_document), + page, &width, &height); + rectangle.x1 = points->x1 / SCALE_FACTOR; + rectangle.y1 = (height - points->y2) / SCALE_FACTOR; + rectangle.x2 = points->x2 / SCALE_FACTOR; + rectangle.y2 = (height - points->y1) / SCALE_FACTOR; + + text = djvu_text_copy (djvu_document, page->index, &rectangle); + + if (text == NULL) + text = g_strdup (""); + + return text; +} + +static void +djvu_selection_iface_init (EvSelectionInterface *iface) +{ + iface->get_selected_text = djvu_selection_get_selected_text; +} + +static void +djvu_document_thumbnails_get_dimensions (EvDocumentThumbnails *document, + EvRenderContext *rc, + gint *width, + gint *height) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document); + gdouble page_width, page_height; + + djvu_document_get_page_size (EV_DOCUMENT(djvu_document), rc->page, + &page_width, &page_height); + + if (rc->rotation == 90 || rc->rotation == 270) { + *width = (gint) (page_height * rc->scale); + *height = (gint) (page_width * rc->scale); + } else { + *width = (gint) (page_width * rc->scale); + *height = (gint) (page_height * rc->scale); + } +} + +static GdkPixbuf * +djvu_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document, + EvRenderContext *rc, + gboolean border) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document); + GdkPixbuf *pixbuf, *rotated_pixbuf; + gdouble page_width, page_height; + gint thumb_width, thumb_height; + guchar *pixels; + + g_return_val_if_fail (djvu_document->d_document, NULL); + + djvu_document_get_page_size (EV_DOCUMENT(djvu_document), rc->page, + &page_width, &page_height); + + thumb_width = (gint) (page_width * rc->scale); + thumb_height = (gint) (page_height * rc->scale); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, + thumb_width, thumb_height); + gdk_pixbuf_fill (pixbuf, 0xffffffff); + pixels = gdk_pixbuf_get_pixels (pixbuf); + + while (ddjvu_thumbnail_status (djvu_document->d_document, rc->page->index, 1) < DDJVU_JOB_OK) + djvu_handle_events(djvu_document, TRUE, NULL); + + ddjvu_thumbnail_render (djvu_document->d_document, rc->page->index, + &thumb_width, &thumb_height, + djvu_document->thumbs_format, + gdk_pixbuf_get_rowstride (pixbuf), + (gchar *)pixels); + + rotated_pixbuf = gdk_pixbuf_rotate_simple (pixbuf, 360 - rc->rotation); + g_object_unref (pixbuf); + + if (border) { + GdkPixbuf *tmp_pixbuf = rotated_pixbuf; + + rotated_pixbuf = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf); + g_object_unref (tmp_pixbuf); + } + + return rotated_pixbuf; +} + +static void +djvu_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface) +{ + iface->get_thumbnail = djvu_document_thumbnails_get_thumbnail; + iface->get_dimensions = djvu_document_thumbnails_get_dimensions; +} + +/* EvFileExporterIface */ +static void +djvu_document_file_exporter_begin (EvFileExporter *exporter, + EvFileExporterContext *fc) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (exporter); + + if (djvu_document->ps_filename) + g_free (djvu_document->ps_filename); + djvu_document->ps_filename = g_strdup (fc->filename); + + g_string_assign (djvu_document->opts, "-page="); +} + +static void +djvu_document_file_exporter_do_page (EvFileExporter *exporter, + EvRenderContext *rc) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (exporter); + + g_string_append_printf (djvu_document->opts, "%d,", (rc->page->index) + 1); +} + +static void +djvu_document_file_exporter_end (EvFileExporter *exporter) +{ + int d_optc = 1; + const char *d_optv[d_optc]; + + DjvuDocument *djvu_document = DJVU_DOCUMENT (exporter); + + FILE *fn = fopen (djvu_document->ps_filename, "w"); + if (fn == NULL) { + g_warning ("Cannot open file ā€œ%sā€.", djvu_document->ps_filename); + return; + } + + d_optv[0] = djvu_document->opts->str; + + ddjvu_job_t * job = ddjvu_document_print(djvu_document->d_document, fn, d_optc, d_optv); + while (!ddjvu_job_done(job)) { + djvu_handle_events (djvu_document, TRUE, NULL); + } + + fclose(fn); +} + +static EvFileExporterCapabilities +djvu_document_file_exporter_get_capabilities (EvFileExporter *exporter) +{ + return EV_FILE_EXPORTER_CAN_PAGE_SET | + EV_FILE_EXPORTER_CAN_COPIES | + EV_FILE_EXPORTER_CAN_COLLATE | + EV_FILE_EXPORTER_CAN_REVERSE | + EV_FILE_EXPORTER_CAN_GENERATE_PS; +} + +static void +djvu_document_file_exporter_iface_init (EvFileExporterInterface *iface) +{ + iface->begin = djvu_document_file_exporter_begin; + iface->do_page = djvu_document_file_exporter_do_page; + iface->end = djvu_document_file_exporter_end; + iface->get_capabilities = djvu_document_file_exporter_get_capabilities; +} + +static void +djvu_document_init (DjvuDocument *djvu_document) +{ + guint masks[4] = { 0xff0000, 0xff00, 0xff, 0xff000000 }; + + djvu_document->d_context = ddjvu_context_create ("Evince"); + djvu_document->d_format = ddjvu_format_create (DDJVU_FORMAT_RGBMASK32, 4, masks); + ddjvu_format_set_row_order (djvu_document->d_format, 1); + + djvu_document->thumbs_format = ddjvu_format_create (DDJVU_FORMAT_RGB24, 0, 0); + ddjvu_format_set_row_order (djvu_document->thumbs_format, 1); + + djvu_document->ps_filename = NULL; + djvu_document->opts = g_string_new (""); + + djvu_document->d_document = NULL; +} + +static GList * +djvu_document_find_find_text (EvDocumentFind *document, + EvPage *page, + const char *text, + gboolean case_sensitive) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document); + miniexp_t page_text; + gdouble width, height; + GList *matches = NULL, *l; + + g_return_val_if_fail (text != NULL, NULL); + + while ((page_text = ddjvu_document_get_pagetext (djvu_document->d_document, + page->index, + "char")) == miniexp_dummy) + djvu_handle_events (djvu_document, TRUE, NULL); + + if (page_text != miniexp_nil) { + DjvuTextPage *tpage = djvu_text_page_new (page_text); + + djvu_text_page_prepare_search (tpage, case_sensitive); + if (tpage->links->len > 0) { + djvu_text_page_search (tpage, text); + matches = tpage->results; + } + djvu_text_page_free (tpage); + ddjvu_miniexp_release (djvu_document->d_document, page_text); + } + + if (!matches) + return NULL; + + document_get_page_size (djvu_document, page->index, &width, &height); + for (l = matches; l && l->data; l = g_list_next (l)) { + EvRectangle *r = (EvRectangle *)l->data; + gdouble tmp; + + tmp = r->y1; + + r->x1 *= SCALE_FACTOR; + r->x2 *= SCALE_FACTOR; + + tmp = r->y1; + r->y1 = height - r->y2 * SCALE_FACTOR; + r->y2 = height - tmp * SCALE_FACTOR; + } + + + return matches; +} + +static void +djvu_document_find_iface_init (EvDocumentFindInterface *iface) +{ + iface->find_text = djvu_document_find_find_text; +} + +static EvMappingList * +djvu_document_links_get_links (EvDocumentLinks *document_links, + EvPage *page) +{ + return djvu_links_get_links (document_links, page->index, SCALE_FACTOR); +} + +static void +djvu_document_document_links_iface_init (EvDocumentLinksInterface *iface) +{ + iface->has_document_links = djvu_links_has_document_links; + iface->get_links_model = djvu_links_get_links_model; + iface->get_links = djvu_document_links_get_links; + iface->find_link_dest = djvu_links_find_link_dest; +} diff --git a/backend/djvu/djvu-document.h b/backend/djvu/djvu-document.h new file mode 100644 index 00000000..bcb916af --- /dev/null +++ b/backend/djvu/djvu-document.h @@ -0,0 +1,38 @@ +/* djvu-document.h: Implementation of EvDocument for djvu documents + * Copyright (C) 2005, Nickolay V. Shmyrev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __DJVU_DOCUMENT_H__ +#define __DJVU_DOCUMENT_H__ + +#include "ev-document.h" + +G_BEGIN_DECLS + +#define DJVU_TYPE_DOCUMENT (djvu_document_get_type ()) +#define DJVU_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DJVU_TYPE_DOCUMENT, DjvuDocument)) +#define DJVU_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DJVU_TYPE_DOCUMENT)) + +typedef struct _DjvuDocument DjvuDocument; + +GType djvu_document_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT GType register_evince_backend (GTypeModule *module); + +G_END_DECLS + +#endif /* __DJVU_DOCUMENT_H__ */ diff --git a/backend/djvu/djvu-links.c b/backend/djvu/djvu-links.c new file mode 100644 index 00000000..d13af0be --- /dev/null +++ b/backend/djvu/djvu-links.c @@ -0,0 +1,434 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* + * Implements hyperlink functionality for Djvu files. + * Copyright (C) 2006 Pauli Virtanen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include "djvu-document.h" +#include "djvu-links.h" +#include "djvu-document-private.h" +#include "ev-document-links.h" +#include "ev-mapping-list.h" + +static gboolean number_from_miniexp(miniexp_t sexp, int *number) +{ + if (miniexp_numberp (sexp)) { + *number = miniexp_to_int (sexp); + return TRUE; + } else { + return FALSE; + } +} + +static gboolean string_from_miniexp(miniexp_t sexp, const char **str) +{ + if (miniexp_stringp (sexp)) { + *str = miniexp_to_str (sexp); + return TRUE; + } else { + return FALSE; + } +} + +static gboolean number_from_string_10(const gchar *str, guint64 *number) +{ + gchar *end_ptr; + + *number = g_ascii_strtoull(str, &end_ptr, 10); + if (*end_ptr == '\0') { + return TRUE; + } else { + return FALSE; + } +} + +static EvLinkDest * +get_djvu_link_dest (const DjvuDocument *djvu_document, const gchar *link_name, int base_page) +{ + guint64 page_num = 0; + + /* #pagenum, #+pageoffset, #-pageoffset */ + if (g_str_has_prefix (link_name, "#")) { + if (base_page > 0 && g_str_has_prefix (link_name+1, "+")) { + if (number_from_string_10 (link_name + 2, &page_num)) { + return ev_link_dest_new_page (base_page + page_num); + } + } else if (base_page > 0 && g_str_has_prefix (link_name+1, "-")) { + if (number_from_string_10 (link_name + 2, &page_num)) { + return ev_link_dest_new_page (base_page - page_num); + } + } else { + if (number_from_string_10 (link_name + 1, &page_num)) { + return ev_link_dest_new_page (page_num - 1); + } + } + } else { + /* FIXME: component file identifiers */ + } + + return NULL; +} + +static EvLinkAction * +get_djvu_link_action (const DjvuDocument *djvu_document, const gchar *link_name, int base_page) +{ + EvLinkDest *ev_dest = NULL; + EvLinkAction *ev_action = NULL; + + ev_dest = get_djvu_link_dest (djvu_document, link_name, base_page); + + if (ev_dest) { + ev_action = ev_link_action_new_dest (ev_dest); + } else if (strstr(link_name, "://") != NULL) { + /* It's probably an URI */ + ev_action = ev_link_action_new_external_uri (link_name); + } else { + /* FIXME: component file identifiers */ + } + + return ev_action; +} + +static gchar * +str_to_utf8 (const gchar *text) +{ + static const gchar *encodings_to_try[2]; + static gint n_encodings_to_try = 0; + gchar *utf8_text = NULL; + gint i; + + if (n_encodings_to_try == 0) { + const gchar *charset; + gboolean charset_is_utf8; + + charset_is_utf8 = g_get_charset (&charset); + if (!charset_is_utf8) { + encodings_to_try[n_encodings_to_try++] = charset; + } + + if (g_ascii_strcasecmp (charset, "ISO-8859-1") != 0) { + encodings_to_try[n_encodings_to_try++] = "ISO-8859-1"; + } + } + + for (i = 0; i < n_encodings_to_try; i++) { + utf8_text = g_convert (text, -1, "UTF-8", + encodings_to_try[i], + NULL, NULL, NULL); + if (utf8_text) + break; + } + + return utf8_text; +} + +/** + * Builds the index GtkTreeModel from DjVu s-expr + * + * (bookmarks + * ("title1" "dest1" + * ("title12" "dest12" + * ... ) + * ... ) + * ("title2" "dest2" + * ... ) + * ... ) + */ +static void +build_tree (const DjvuDocument *djvu_document, + GtkTreeModel *model, + GtkTreeIter *parent, + miniexp_t iter) +{ + const char *title, *link_dest; + char *title_markup; + + EvLinkAction *ev_action = NULL; + EvLink *ev_link = NULL; + GtkTreeIter tree_iter; + + if (miniexp_car (iter) == miniexp_symbol ("bookmarks")) { + /* The (bookmarks) cons */ + iter = miniexp_cdr (iter); + } else if ( miniexp_length (iter) >= 2 ) { + gchar *utf8_title = NULL; + + /* An entry */ + if (!string_from_miniexp (miniexp_car (iter), &title)) goto unknown_entry; + if (!string_from_miniexp (miniexp_cadr (iter), &link_dest)) goto unknown_entry; + + + if (!g_utf8_validate (title, -1, NULL)) { + utf8_title = str_to_utf8 (title); + title_markup = g_markup_escape_text (utf8_title, -1); + } else { + title_markup = g_markup_escape_text (title, -1); + } + + ev_action = get_djvu_link_action (djvu_document, link_dest, -1); + + if (g_str_has_suffix (link_dest, ".djvu")) { + /* FIXME: component file identifiers */ + } else if (ev_action) { + ev_link = ev_link_new (utf8_title ? utf8_title : title, ev_action); + gtk_tree_store_append (GTK_TREE_STORE (model), &tree_iter, parent); + gtk_tree_store_set (GTK_TREE_STORE (model), &tree_iter, + EV_DOCUMENT_LINKS_COLUMN_MARKUP, title_markup, + EV_DOCUMENT_LINKS_COLUMN_LINK, ev_link, + EV_DOCUMENT_LINKS_COLUMN_EXPAND, FALSE, + -1); + g_object_unref (ev_link); + } else { + gtk_tree_store_append (GTK_TREE_STORE (model), &tree_iter, parent); + gtk_tree_store_set (GTK_TREE_STORE (model), &tree_iter, + EV_DOCUMENT_LINKS_COLUMN_MARKUP, title_markup, + EV_DOCUMENT_LINKS_COLUMN_EXPAND, FALSE, + -1); + } + + g_free (title_markup); + g_free (utf8_title); + iter = miniexp_cddr (iter); + parent = &tree_iter; + } else { + goto unknown_entry; + } + + for (; iter != miniexp_nil; iter = miniexp_cdr (iter)) { + build_tree (djvu_document, model, parent, miniexp_car (iter)); + } + return; + + unknown_entry: + g_warning ("DjvuLibre error: Unknown entry in bookmarks"); + return; +} + +static gboolean +get_djvu_hyperlink_area (ddjvu_pageinfo_t *page_info, + miniexp_t sexp, + EvMapping *ev_link_mapping) +{ + miniexp_t iter; + + iter = sexp; + + if ((miniexp_car (iter) == miniexp_symbol ("rect") || miniexp_car (iter) == miniexp_symbol ("oval")) + && miniexp_length (iter) == 5) { + /* FIXME: get bounding box for (oval) since Evince doesn't support shaped links */ + int minx, miny, width, height; + + iter = miniexp_cdr (iter); + if (!number_from_miniexp (miniexp_car (iter), &minx)) goto unknown_link; + iter = miniexp_cdr (iter); + if (!number_from_miniexp (miniexp_car (iter), &miny)) goto unknown_link; + iter = miniexp_cdr (iter); + if (!number_from_miniexp (miniexp_car (iter), &width)) goto unknown_link; + iter = miniexp_cdr (iter); + if (!number_from_miniexp (miniexp_car (iter), &height)) goto unknown_link; + + ev_link_mapping->area.x1 = minx; + ev_link_mapping->area.x2 = (minx + width); + ev_link_mapping->area.y1 = (page_info->height - (miny + height)); + ev_link_mapping->area.y2 = (page_info->height - miny); + } else if (miniexp_car (iter) == miniexp_symbol ("poly") + && miniexp_length (iter) >= 5 && miniexp_length (iter) % 2 == 1) { + + /* FIXME: get bounding box since Evince doesn't support shaped links */ + int minx = G_MAXINT, miny = G_MAXINT; + int maxx = G_MININT, maxy = G_MININT; + + iter = miniexp_cdr(iter); + while (iter != miniexp_nil) { + int x, y; + + if (!number_from_miniexp (miniexp_car(iter), &x)) goto unknown_link; + iter = miniexp_cdr (iter); + if (!number_from_miniexp (miniexp_car(iter), &y)) goto unknown_link; + iter = miniexp_cdr (iter); + + minx = MIN (minx, x); + miny = MIN (miny, y); + maxx = MAX (maxx, x); + maxy = MAX (maxy, y); + } + + ev_link_mapping->area.x1 = minx; + ev_link_mapping->area.x2 = maxx; + ev_link_mapping->area.y1 = (page_info->height - maxy); + ev_link_mapping->area.y2 = (page_info->height - miny); + } else { + /* unknown */ + goto unknown_link; + } + + return TRUE; + + unknown_link: + g_warning("DjvuLibre error: Unknown hyperlink area %s", miniexp_to_name(miniexp_car(sexp))); + return FALSE; +} + +static EvMapping * +get_djvu_hyperlink_mapping (DjvuDocument *djvu_document, + int page, + ddjvu_pageinfo_t *page_info, + miniexp_t sexp) +{ + EvMapping *ev_link_mapping = NULL; + EvLinkAction *ev_action = NULL; + miniexp_t iter; + const char *url, *url_target, *comment; + + ev_link_mapping = g_new (EvMapping, 1); + + iter = sexp; + + if (miniexp_car (iter) != miniexp_symbol ("maparea")) goto unknown_mapping; + + iter = miniexp_cdr(iter); + + if (miniexp_caar(iter) == miniexp_symbol("url")) { + if (!string_from_miniexp (miniexp_cadr (miniexp_car (iter)), &url)) goto unknown_mapping; + if (!string_from_miniexp (miniexp_caddr (miniexp_car (iter)), &url_target)) goto unknown_mapping; + } else { + if (!string_from_miniexp (miniexp_car(iter), &url)) goto unknown_mapping; + url_target = NULL; + } + + iter = miniexp_cdr (iter); + if (!string_from_miniexp (miniexp_car(iter), &comment)) goto unknown_mapping; + + iter = miniexp_cdr (iter); + if (!get_djvu_hyperlink_area (page_info, miniexp_car(iter), ev_link_mapping)) goto unknown_mapping; + + iter = miniexp_cdr (iter); + /* FIXME: DjVu hyperlink attributes are ignored */ + + ev_action = get_djvu_link_action (djvu_document, url, page); + if (!ev_action) goto unknown_mapping; + + ev_link_mapping->data = ev_link_new (comment, ev_action); + + return ev_link_mapping; + + unknown_mapping: + if (ev_link_mapping) g_free(ev_link_mapping); + g_warning("DjvuLibre error: Unknown hyperlink %s", miniexp_to_name(miniexp_car(sexp))); + return NULL; +} + + +gboolean +djvu_links_has_document_links (EvDocumentLinks *document_links) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document_links); + miniexp_t outline; + + while ((outline = ddjvu_document_get_outline (djvu_document->d_document)) == miniexp_dummy) + djvu_handle_events (djvu_document, TRUE, NULL); + + if (outline) { + ddjvu_miniexp_release (djvu_document->d_document, outline); + return TRUE; + } + + return FALSE; +} + +EvMappingList * +djvu_links_get_links (EvDocumentLinks *document_links, + gint page, + double scale_factor) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document_links); + GList *retval = NULL; + miniexp_t page_annotations = miniexp_nil; + miniexp_t *hyperlinks = NULL, *iter = NULL; + EvMapping *ev_link_mapping; + ddjvu_pageinfo_t page_info; + + while ((page_annotations = ddjvu_document_get_pageanno (djvu_document->d_document, page)) == miniexp_dummy) + djvu_handle_events (djvu_document, TRUE, NULL); + + while (ddjvu_document_get_pageinfo (djvu_document->d_document, page, &page_info) < DDJVU_JOB_OK) + djvu_handle_events(djvu_document, TRUE, NULL); + + if (page_annotations) { + hyperlinks = ddjvu_anno_get_hyperlinks (page_annotations); + if (hyperlinks) { + for (iter = hyperlinks; *iter; ++iter) { + ev_link_mapping = get_djvu_hyperlink_mapping (djvu_document, page, &page_info, *iter); + if (ev_link_mapping) { + ev_link_mapping->area.x1 *= scale_factor; + ev_link_mapping->area.x2 *= scale_factor; + ev_link_mapping->area.y1 *= scale_factor; + ev_link_mapping->area.y2 *= scale_factor; + retval = g_list_prepend (retval, ev_link_mapping); + } + } + free (hyperlinks); + } + ddjvu_miniexp_release (djvu_document->d_document, page_annotations); + } + + return ev_mapping_list_new (page, retval, (GDestroyNotify)g_object_unref); +} + +EvLinkDest * +djvu_links_find_link_dest (EvDocumentLinks *document_links, + const gchar *link_name) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document_links); + EvLinkDest *ev_dest = NULL; + + ev_dest = get_djvu_link_dest (djvu_document, link_name, -1); + + if (!ev_dest) { + g_warning ("DjvuLibre error: unknown link destination %s", link_name); + } + + return ev_dest; +} + +GtkTreeModel * +djvu_links_get_links_model (EvDocumentLinks *document_links) +{ + DjvuDocument *djvu_document = DJVU_DOCUMENT (document_links); + GtkTreeModel *model = NULL; + miniexp_t outline = miniexp_nil; + + while ((outline = ddjvu_document_get_outline (djvu_document->d_document)) == miniexp_dummy) + djvu_handle_events (djvu_document, TRUE, NULL); + + if (outline) { + model = (GtkTreeModel *) gtk_tree_store_new (EV_DOCUMENT_LINKS_COLUMN_NUM_COLUMNS, + G_TYPE_STRING, + G_TYPE_OBJECT, + G_TYPE_BOOLEAN, + G_TYPE_STRING); + build_tree (djvu_document, model, NULL, outline); + + ddjvu_miniexp_release (djvu_document->d_document, outline); + } + + return model; +} diff --git a/backend/djvu/djvu-links.h b/backend/djvu/djvu-links.h new file mode 100644 index 00000000..76d9072c --- /dev/null +++ b/backend/djvu/djvu-links.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2006 Pauli Virtanen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __DJVU_LINK_H__ +#define __DJVU_LINK_H__ + +#include "ev-document-links.h" +#include "djvu-document.h" + +#include + +GtkTreeModel *djvu_links_get_links_model (EvDocumentLinks *document_links); +EvMappingList *djvu_links_get_links (EvDocumentLinks *document_links, + gint page, + double scale_factor); +EvLinkDest *djvu_links_find_link_dest (EvDocumentLinks *document_links, + const gchar *link_name); +gboolean djvu_links_has_document_links (EvDocumentLinks *document_links); + +#endif /* __DJVU_LINK_H__ */ diff --git a/backend/djvu/djvu-text-page.c b/backend/djvu/djvu-text-page.c new file mode 100644 index 00000000..3f171d1e --- /dev/null +++ b/backend/djvu/djvu-text-page.c @@ -0,0 +1,445 @@ +/* + * Implements search and copy functionality for Djvu files. + * Copyright (C) 2006 Michael Hofmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include "djvu-text-page.h" + + +/** + * djvu_text_page_selection_process: + * @page: #DjvuTextPage instance + * @p: s-expression to append + * @delimit: character/word/... delimiter + * + * Appends the string in @p to the page text. + * + * Returns: whether the end was not reached in this s-expression + */ +static gboolean +djvu_text_page_selection_process (DjvuTextPage *page, + miniexp_t p, + int delimit) +{ + if (page->text || p == page->start) { + char *token_text = (char *) miniexp_to_str (miniexp_nth (5, p)); + if (page->text) { + char *new_text = + g_strjoin (delimit & 2 ? "\n" : + delimit & 1 ? " " : NULL, + page->text, token_text, + NULL); + g_free (page->text); + page->text = new_text; + } else + page->text = g_strdup (token_text); + if (p == page->end) + return FALSE; + } + return TRUE; +} + +/** + * djvu_text_page_selection: + * @page: #DjvuTextPage instance + * @p: tree to append + * @delimit: character/word/... delimiter + * + * Walks the tree in @p and appends the text with + * djvu_text_page_selection_process() for all s-expressions + * between the start and end fields. + * + * Returns: whether the end was not reached in this subtree + */ +static gboolean +djvu_text_page_selection (DjvuTextPage *page, + miniexp_t p, + int delimit) +{ + g_return_val_if_fail (miniexp_consp (p) && miniexp_symbolp + (miniexp_car (p)), FALSE); + + if (miniexp_car (p) != page->char_symbol) + delimit |= miniexp_car (p) == page->word_symbol ? 1 : 2; + + miniexp_t deeper = miniexp_cddr (miniexp_cdddr (p)); + while (deeper != miniexp_nil) { + miniexp_t str = miniexp_car (deeper); + if (miniexp_stringp (str)) { + if (!djvu_text_page_selection_process + (page, p, delimit)) + return FALSE; + } else { + if (!djvu_text_page_selection + (page, str, delimit)) + return FALSE; + } + delimit = 0; + deeper = miniexp_cdr (deeper); + } + return TRUE; +} + +static void +djvu_text_page_limits_process (DjvuTextPage *page, + miniexp_t p, + EvRectangle *rect) +{ + EvRectangle current; + + current.x1 = miniexp_to_int (miniexp_nth (1, p)); + current.y1 = miniexp_to_int (miniexp_nth (2, p)); + current.x2 = miniexp_to_int (miniexp_nth (3, p)); + current.y2 = miniexp_to_int (miniexp_nth (4, p)); + if (current.x2 >= rect->x1 && current.y1 <= rect->y2 && + current.x1 <= rect->x2 && current.y2 >= rect->y1) { + if (page->start == miniexp_nil) + page->start = p; + page->end = p; + } +} + + +static void +djvu_text_page_limits (DjvuTextPage *page, + miniexp_t p, + EvRectangle *rect) +{ + g_return_if_fail (miniexp_consp (p) && + miniexp_symbolp (miniexp_car (p))); + + miniexp_t deeper = miniexp_cddr (miniexp_cdddr (p)); + while (deeper != miniexp_nil) { + miniexp_t str = miniexp_car (deeper); + if (miniexp_stringp (str)) + djvu_text_page_limits_process (page, p, rect); + else + djvu_text_page_limits (page, str, rect); + + deeper = miniexp_cdr (deeper); + } +} + +char * +djvu_text_page_copy (DjvuTextPage *page, + EvRectangle *rectangle) +{ + char* text; + + page->start = miniexp_nil; + page->end = miniexp_nil; + djvu_text_page_limits (page, page->text_structure, rectangle); + djvu_text_page_selection (page, page->text_structure, 0); + + /* Do not free the string */ + text = page->text; + page->text = NULL; + + return text; +} + +/** + * djvu_text_page_position: + * @page: #DjvuTextPage instance + * @position: index in the page text + * + * Returns the closest s-expression that contains the given position in + * the page text. + * + * Returns: closest s-expression + */ +static miniexp_t +djvu_text_page_position (DjvuTextPage *page, + int position) +{ + GArray *links = page->links; + int low = 0; + int hi = links->len - 1; + int mid = 0; + + g_return_val_if_fail (hi >= 0, miniexp_nil); + + /* Shamelessly copied from GNU classpath */ + while (low <= hi) { + mid = (low + hi) >> 1; + DjvuTextLink *link = + &g_array_index (links, DjvuTextLink, mid); + if (link->position == position) + break; + else if (link->position > position) + hi = --mid; + else + low = mid + 1; + } + + return g_array_index (page->links, DjvuTextLink, mid).pair; +} + +/** + * djvu_text_page_union: + * @target: first rectangle and result + * @source: second rectangle + * + * Calculates the bounding box of two rectangles and stores the reuslt + * in the first. + */ +static void +djvu_text_page_union (EvRectangle *target, + EvRectangle *source) +{ + if (source->x1 < target->x1) + target->x1 = source->x1; + if (source->x2 > target->x2) + target->x2 = source->x2; + if (source->y1 < target->y1) + target->y1 = source->y1; + if (source->y2 > target->y2) + target->y2 = source->y2; +} + +/** + * djvu_text_page_sexpr_process: + * @page: #DjvuTextPage instance + * @p: s-expression to append + * @start: first s-expression in the selection + * @end: last s-expression in the selection + * + * Appends the rectangle defined by @p to the internal bounding box rectangle. + * + * Returns: whether the end was not reached in this s-expression + */ +static gboolean +djvu_text_page_sexpr_process (DjvuTextPage *page, + miniexp_t p, + miniexp_t start, + miniexp_t end) +{ + if (page->bounding_box || p == start) { + EvRectangle *new_rectangle = ev_rectangle_new (); + new_rectangle->x1 = miniexp_to_int (miniexp_nth (1, p)); + new_rectangle->y1 = miniexp_to_int (miniexp_nth (2, p)); + new_rectangle->x2 = miniexp_to_int (miniexp_nth (3, p)); + new_rectangle->y2 = miniexp_to_int (miniexp_nth (4, p)); + if (page->bounding_box) { + djvu_text_page_union (page->bounding_box, + new_rectangle); + g_free (new_rectangle); + } else + page->bounding_box = new_rectangle; + if (p == end) + return FALSE; + } + return TRUE; +} + +/** + * djvu_text_page_sexpr: + * @page: #DjvuTextPage instance + * @p: tree to append + * @start: first s-expression in the selection + * @end: last s-expression in the selection + * + * Walks the tree in @p and extends the rectangle with + * djvu_text_page_process() for all s-expressions between @start and @end. + * + * Returns: whether the end was not reached in this subtree + */ +static gboolean +djvu_text_page_sexpr (DjvuTextPage *page, + miniexp_t p, + miniexp_t start, + miniexp_t end) +{ + g_return_val_if_fail (miniexp_consp (p) && miniexp_symbolp + (miniexp_car (p)), FALSE); + + miniexp_t deeper = miniexp_cddr (miniexp_cdddr (p)); + while (deeper != miniexp_nil) { + miniexp_t str = miniexp_car (deeper); + if (miniexp_stringp (str)) { + if (!djvu_text_page_sexpr_process + (page, p, start, end)) + return FALSE; + } else { + if (!djvu_text_page_sexpr + (page, str, start, end)) + return FALSE; + } + deeper = miniexp_cdr (deeper); + } + return TRUE; +} + +/** + * djvu_text_page_box: + * @page: #DjvuTextPage instance + * @start: first s-expression in the selection + * @end: last s-expression in the selection + * + * Builds a rectangle that contains all s-expressions in the given range. + */ +static EvRectangle * +djvu_text_page_box (DjvuTextPage *page, + miniexp_t start, + miniexp_t end) +{ + page->bounding_box = NULL; + djvu_text_page_sexpr (page, page->text_structure, start, end); + return page->bounding_box; +} + +/** + * djvu_text_page_append_search: + * @page: #DjvuTextPage instance + * @p: tree to append + * @case_sensitive: do not ignore case + * @delimit: insert spaces because of higher (sentence/paragraph/...) break + * + * Appends the tree in @p to the internal text string. + */ +static void +djvu_text_page_append_text (DjvuTextPage *page, + miniexp_t p, + gboolean case_sensitive, + gboolean delimit) +{ + char *token_text; + + g_return_if_fail (miniexp_consp (p) && + miniexp_symbolp (miniexp_car (p))); + + delimit |= page->char_symbol != miniexp_car (p); + + miniexp_t deeper = miniexp_cddr (miniexp_cdddr (p)); + while (deeper != miniexp_nil) { + miniexp_t data = miniexp_car (deeper); + if (miniexp_stringp (data)) { + DjvuTextLink link; + link.position = page->text == NULL ? 0 : + strlen (page->text); + link.pair = p; + g_array_append_val (page->links, link); + + token_text = (char *) miniexp_to_str (data); + if (!case_sensitive) + token_text = g_utf8_casefold (token_text, -1); + if (page->text == NULL) + page->text = g_strdup (token_text); + else { + char *new_text = + g_strjoin (delimit ? " " : NULL, + page->text, token_text, + NULL); + g_free (page->text); + page->text = new_text; + } + if (!case_sensitive) + g_free (token_text); + } else + djvu_text_page_append_text (page, data, + case_sensitive, delimit); + delimit = FALSE; + deeper = miniexp_cdr (deeper); + } +} + +/** + * djvu_text_page_search: + * @page: #DjvuTextPage instance + * @text: text to search + * + * Searches the page for the given text. The results list has to be + * externally freed afterwards. + */ +void +djvu_text_page_search (DjvuTextPage *page, + const char *text) +{ + char *haystack = page->text; + int search_len; + EvRectangle *result; + if (page->links->len == 0) + return; + + search_len = strlen (text); + while ((haystack = strstr (haystack, text)) != NULL) { + int start_p = haystack - page->text; + miniexp_t start = djvu_text_page_position (page, start_p); + int end_p = start_p + search_len - 1; + miniexp_t end = djvu_text_page_position (page, end_p); + result = djvu_text_page_box (page, start, end); + g_assert (result); + page->results = g_list_prepend (page->results, result); + haystack = haystack + search_len; + } + page->results = g_list_reverse (page->results); +} + + +/** + * djvu_text_page_prepare_search: + * @page: #DjvuTextPage instance + * @case_sensitive: do not ignore case + * + * Indexes the page text and prepares the page for subsequent searches. + */ +void +djvu_text_page_prepare_search (DjvuTextPage *page, + gboolean case_sensitive) +{ + djvu_text_page_append_text (page, page->text_structure, + case_sensitive, FALSE); +} + +/** + * djvu_text_page_new: + * @text: S-expression of the page text + * + * Creates a new page to search. + * + * Returns: new #DjvuTextPage instance + */ +DjvuTextPage * +djvu_text_page_new (miniexp_t text) +{ + DjvuTextPage *page; + + page = g_new0 (DjvuTextPage, 1); + page->links = g_array_new (FALSE, FALSE, sizeof (DjvuTextLink)); + page->char_symbol = miniexp_symbol ("char"); + page->word_symbol = miniexp_symbol ("word"); + page->text_structure = text; + return page; +} + +/** + * djvu_text_page_free: + * @page: #DjvuTextPage instance + * + * Frees the given #DjvuTextPage instance. + */ +void +djvu_text_page_free (DjvuTextPage *page) +{ + g_free (page->text); + g_array_free (page->links, TRUE); + g_free (page); +} diff --git a/backend/djvu/djvu-text-page.h b/backend/djvu/djvu-text-page.h new file mode 100644 index 00000000..6e16f259 --- /dev/null +++ b/backend/djvu/djvu-text-page.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2006 Michael Hofmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __DJVU_TEXT_PAGE_H__ +#define __DJVU_TEXT_PAGE_H__ + +#include "ev-document.h" + +#include +#include +#include + + +typedef struct _DjvuTextPage DjvuTextPage; +typedef struct _DjvuTextLink DjvuTextLink; + +struct _DjvuTextPage { + char *text; + GArray *links; + GList *results; + miniexp_t char_symbol; + miniexp_t word_symbol; + EvRectangle *bounding_box; + miniexp_t text_structure; + miniexp_t start; + miniexp_t end; +}; + +struct _DjvuTextLink { + int position; + miniexp_t pair; +}; + +char * djvu_text_page_copy (DjvuTextPage *page, + EvRectangle *rectangle); +void djvu_text_page_prepare_search (DjvuTextPage *page, + gboolean case_sensitive); +void djvu_text_page_search (DjvuTextPage *page, + const char *text); +DjvuTextPage* djvu_text_page_new (miniexp_t text); +void djvu_text_page_free (DjvuTextPage *page); + +#endif /* __DJVU_TEXT_PAGE_H__ */ + diff --git a/backend/djvu/djvudocument.evince-backend.in b/backend/djvu/djvudocument.evince-backend.in new file mode 100644 index 00000000..485af118 --- /dev/null +++ b/backend/djvu/djvudocument.evince-backend.in @@ -0,0 +1,4 @@ +[Evince Backend] +Module=djvudocument +_TypeDescription=DjVu Documents +MimeType=image/vnd.djvu diff --git a/backend/dvi/Makefile.am b/backend/dvi/Makefile.am new file mode 100644 index 00000000..6dcd50ed --- /dev/null +++ b/backend/dvi/Makefile.am @@ -0,0 +1,47 @@ +SUBDIRS = mdvi-lib + +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/libdocument \ + -I$(srcdir)/mdvi-lib \ + -DMATELOCALEDIR=\"$(datadir)/locale\" \ + -DEVINCE_COMPILATION \ + $(WARN_CFLAGS) \ + $(BACKEND_CFLAGS) \ + $(SPECTRE_CFLAGS) \ + $(DISABLE_DEPRECATED) + +backend_LTLIBRARIES = libdvidocument.la + +libdvidocument_la_SOURCES = \ + dvi-document.c \ + dvi-document.h \ + cairo-device.c \ + cairo-device.h \ + texmfcnf.c \ + texmfcnf.h \ + fonts.c \ + fonts.h + +libdvidocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS) +libdvidocument_la_LIBADD = \ + mdvi-lib/libmdvi.la \ + -lkpathsea \ + $(top_builddir)/libdocument/libevdocument.la \ + $(BACKEND_LIBS) \ + $(SPECTRE_LIBS) + +if WITH_TYPE1_FONTS +libdvidocument_la_LIBADD += -lt1 +endif + +backend_in_files = dvidocument.evince-backend.in +backend_DATA = $(backend_in_files:.evince-backend.in=.evince-backend) + +EXTRA_DIST = $(backend_in_files) + +CLEANFILES = $(backend_DATA) + +@EV_INTLTOOL_EVINCE_BACKEND_RULE@ + +-include $(top_srcdir)/git.mk diff --git a/backend/dvi/cairo-device.c b/backend/dvi/cairo-device.c new file mode 100644 index 00000000..47425cad --- /dev/null +++ b/backend/dvi/cairo-device.c @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2007 Carlos Garcia Campos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include +#ifdef HAVE_SPECTRE +#include +#endif + +#include "cairo-device.h" + +typedef struct { + cairo_t *cr; + + gint xmargin; + gint ymargin; + + gdouble scale; + + Ulong fg; + Ulong bg; + +} DviCairoDevice; + +static void +dvi_cairo_draw_glyph (DviContext *dvi, + DviFontChar *ch, + int x0, + int y0) +{ + DviCairoDevice *cairo_device; + int x, y, w, h; + gboolean isbox; + DviGlyph *glyph; + cairo_surface_t *surface; + + cairo_device = (DviCairoDevice *) dvi->device.device_data; + + glyph = &ch->grey; + + isbox = (glyph->data == NULL || + (dvi->params.flags & MDVI_PARAM_CHARBOXES) || + MDVI_GLYPH_ISEMPTY (glyph->data)); + + x = - glyph->x + x0 + cairo_device->xmargin; + y = - glyph->y + y0 + cairo_device->ymargin; + w = glyph->w; + h = glyph->h; + + surface = cairo_get_target (cairo_device->cr); + if (x < 0 || y < 0 + || x + w > cairo_image_surface_get_width (surface) + || y + h > cairo_image_surface_get_height (surface)) + return; + + cairo_save (cairo_device->cr); + if (isbox) { + cairo_rectangle (cairo_device->cr, + x - cairo_device->xmargin, + y - cairo_device->ymargin, + w, h); + cairo_stroke (cairo_device->cr); + } else { + cairo_translate (cairo_device->cr, x, y); + cairo_set_source_surface (cairo_device->cr, + (cairo_surface_t *) glyph->data, + 0, 0); + cairo_paint (cairo_device->cr); + } + + cairo_restore (cairo_device->cr); +} + +static void +dvi_cairo_draw_rule (DviContext *dvi, + int x, + int y, + Uint width, + Uint height, + int fill) +{ + DviCairoDevice *cairo_device; + Ulong color; + + cairo_device = (DviCairoDevice *) dvi->device.device_data; + + color = cairo_device->fg; + + cairo_save (cairo_device->cr); + + cairo_set_line_width (cairo_device->cr, + cairo_get_line_width (cairo_device->cr) * cairo_device->scale); + cairo_set_source_rgb (cairo_device->cr, + ((color >> 16) & 0xff) / 255., + ((color >> 8) & 0xff) / 255., + ((color >> 0) & 0xff) / 255.); + + cairo_rectangle (cairo_device->cr, + x + cairo_device->xmargin, + y + cairo_device->ymargin, + width, height); + if (fill == 0) { + cairo_stroke (cairo_device->cr); + } else { + cairo_fill (cairo_device->cr); + } + + cairo_restore (cairo_device->cr); +} + +#ifdef HAVE_SPECTRE +static void +dvi_cairo_draw_ps (DviContext *dvi, + const char *filename, + int x, + int y, + Uint width, + Uint height) +{ + DviCairoDevice *cairo_device; + unsigned char *data = NULL; + int row_length; + SpectreDocument *psdoc; + SpectreRenderContext *rc; + int w, h; + SpectreStatus status; + cairo_surface_t *image; + + cairo_device = (DviCairoDevice *) dvi->device.device_data; + + psdoc = spectre_document_new (); + spectre_document_load (psdoc, filename); + if (spectre_document_status (psdoc)) { + spectre_document_free (psdoc); + return; + } + + spectre_document_get_page_size (psdoc, &w, &h); + + rc = spectre_render_context_new (); + spectre_render_context_set_scale (rc, + (double)width / w, + (double)height / h); + spectre_document_render_full (psdoc, rc, &data, &row_length); + status = spectre_document_status (psdoc); + + spectre_render_context_free (rc); + spectre_document_free (psdoc); + + if (status) { + g_warning ("Error rendering PS document %s: %s\n", + filename, spectre_status_to_string (status)); + free (data); + + return; + } + + image = cairo_image_surface_create_for_data ((unsigned char *)data, + CAIRO_FORMAT_RGB24, + width, height, + row_length); + + cairo_save (cairo_device->cr); + + cairo_translate (cairo_device->cr, + x + cairo_device->xmargin, + y + cairo_device->ymargin); + cairo_set_source_surface (cairo_device->cr, image, 0, 0); + cairo_paint (cairo_device->cr); + + cairo_restore (cairo_device->cr); + + cairo_surface_destroy (image); + free (data); +} +#endif /* HAVE_SPECTRE */ + +static int +dvi_cairo_alloc_colors (void *device_data, + Ulong *pixels, + int npixels, + Ulong fg, + Ulong bg, + double gamma, + int density) +{ + double frac; + GdkColor color, color_fg, color_bg; + int i, n; + + color_bg.red = (bg >> 16) & 0xff; + color_bg.green = (bg >> 8) & 0xff; + color_bg.blue = (bg >> 0) & 0xff; + + color_fg.red = (fg >> 16) & 0xff; + color_fg.green = (fg >> 8) & 0xff; + color_fg.blue = (fg >> 0) & 0xff; + + n = npixels - 1; + for (i = 0; i < npixels; i++) { + frac = (gamma > 0) ? + pow ((double)i / n, 1 / gamma) : + 1 - pow ((double)(n - i) / n, -gamma); + + color.red = frac * ((double)color_fg.red - color_bg.red) + color_bg.red; + color.green = frac * ((double)color_fg.green - color_bg.green) + color_bg.green; + color.blue = frac * ((double)color_fg.blue - color_bg.blue) + color_bg.blue; + + pixels[i] = (color.red << 16) + (color.green << 8) + color.blue + 0xff000000; + } + + return npixels; +} + +static void * +dvi_cairo_create_image (void *device_data, + Uint width, + Uint height, + Uint bpp) +{ + return cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height); +} + +static void +dvi_cairo_free_image (void *ptr) +{ + cairo_surface_destroy ((cairo_surface_t *)ptr); +} + +static void +dvi_cairo_put_pixel (void *image, int x, int y, Ulong color) +{ + cairo_surface_t *surface; + gint rowstride; + guint32 *p; + + surface = (cairo_surface_t *) image; + + rowstride = cairo_image_surface_get_stride (surface); + p = (guint32*) (cairo_image_surface_get_data (surface) + y * rowstride + x * 4); + + *p = color; +} + +static void +dvi_cairo_set_color (void *device_data, Ulong fg, Ulong bg) +{ + DviCairoDevice *cairo_device = (DviCairoDevice *) device_data; + + cairo_device->fg = fg; + cairo_device->bg = bg; +} + +/* Public methods */ +void +mdvi_cairo_device_init (DviDevice *device) +{ + device->device_data = g_new0 (DviCairoDevice, 1); + + device->draw_glyph = dvi_cairo_draw_glyph; + device->draw_rule = dvi_cairo_draw_rule; + device->alloc_colors = dvi_cairo_alloc_colors; + device->create_image = dvi_cairo_create_image; + device->free_image = dvi_cairo_free_image; + device->put_pixel = dvi_cairo_put_pixel; + device->set_color = dvi_cairo_set_color; +#ifdef HAVE_SPECTRE + device->draw_ps = dvi_cairo_draw_ps; +#else + device->draw_ps = NULL; +#endif + device->refresh = NULL; +} + +void +mdvi_cairo_device_free (DviDevice *device) +{ + DviCairoDevice *cairo_device; + + cairo_device = (DviCairoDevice *) device->device_data; + + if (cairo_device->cr) + cairo_destroy (cairo_device->cr); + + g_free (cairo_device); +} + +cairo_surface_t * +mdvi_cairo_device_get_surface (DviDevice *device) +{ + DviCairoDevice *cairo_device; + + cairo_device = (DviCairoDevice *) device->device_data; + + return cairo_surface_reference (cairo_get_target (cairo_device->cr)); +} + +void +mdvi_cairo_device_render (DviContext* dvi) +{ + DviCairoDevice *cairo_device; + gint page_width; + gint page_height; + cairo_surface_t *surface; + guchar *pixels; + gint rowstride; + static const cairo_user_data_key_t key; + + cairo_device = (DviCairoDevice *) dvi->device.device_data; + + if (cairo_device->cr) + cairo_destroy (cairo_device->cr); + + page_width = dvi->dvi_page_w * dvi->params.conv + 2 * cairo_device->xmargin; + page_height = dvi->dvi_page_h * dvi->params.vconv + 2 * cairo_device->ymargin; + + rowstride = page_width * 4; + pixels = (guchar *) g_malloc (page_height * rowstride); + memset (pixels, 0xff, page_height * rowstride); + + surface = cairo_image_surface_create_for_data (pixels, + CAIRO_FORMAT_RGB24, + page_width, page_height, + rowstride); + cairo_surface_set_user_data (surface, &key, + pixels, (cairo_destroy_func_t)g_free); + + cairo_device->cr = cairo_create (surface); + cairo_surface_destroy (surface); + + mdvi_dopage (dvi, dvi->currpage); +} + +void +mdvi_cairo_device_set_margins (DviDevice *device, + gint xmargin, + gint ymargin) +{ + DviCairoDevice *cairo_device; + + cairo_device = (DviCairoDevice *) device->device_data; + + cairo_device->xmargin = xmargin; + cairo_device->ymargin = ymargin; +} + +void +mdvi_cairo_device_set_scale (DviDevice *device, + gdouble scale) +{ + DviCairoDevice *cairo_device; + + cairo_device = (DviCairoDevice *) device->device_data; + + cairo_device->scale = scale; +} diff --git a/backend/dvi/cairo-device.h b/backend/dvi/cairo-device.h new file mode 100644 index 00000000..bf76d2af --- /dev/null +++ b/backend/dvi/cairo-device.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 Carlos Garcia Campos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MDVI_CAIRO_DEVICE +#define MDVI_CAIRO_DEVICE + +#include +#include + +#include "mdvi.h" + +G_BEGIN_DECLS + +void mdvi_cairo_device_init (DviDevice *device); +void mdvi_cairo_device_free (DviDevice *device); +cairo_surface_t *mdvi_cairo_device_get_surface (DviDevice *device); +void mdvi_cairo_device_render (DviContext* dvi); +void mdvi_cairo_device_set_margins (DviDevice *device, + gint xmargin, + gint ymargin); +void mdvi_cairo_device_set_scale (DviDevice *device, + gdouble scale); + +G_END_DECLS + +#endif /* MDVI_CAIRO_DEVICE */ diff --git a/backend/dvi/dvi-document.c b/backend/dvi/dvi-document.c new file mode 100644 index 00000000..a4a3dc6d --- /dev/null +++ b/backend/dvi/dvi-document.c @@ -0,0 +1,610 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* + * Copyright (C) 2005, Nickolay V. Shmyrev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include "dvi-document.h" +#include "texmfcnf.h" +#include "ev-document-thumbnails.h" +#include "ev-document-misc.h" +#include "ev-file-exporter.h" +#include "ev-file-helpers.h" + +#include "mdvi.h" +#include "fonts.h" +#include "color.h" +#include "cairo-device.h" + +#include +#include +#ifdef G_OS_WIN32 +# define WIFEXITED(x) ((x) != 3) +# define WEXITSTATUS(x) (x) +#else +# include +#endif +#include + +GMutex *dvi_context_mutex = NULL; + +enum { + PROP_0, + PROP_TITLE +}; + +struct _DviDocumentClass +{ + EvDocumentClass parent_class; +}; + +struct _DviDocument +{ + EvDocument parent_instance; + + DviContext *context; + DviPageSpec *spec; + DviParams *params; + + /* To let document scale we should remember width and height */ + double base_width; + double base_height; + + gchar *uri; + + /* PDF exporter */ + gchar *exporter_filename; + GString *exporter_opts; +}; + +typedef struct _DviDocumentClass DviDocumentClass; + +static void dvi_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); +static void dvi_document_file_exporter_iface_init (EvFileExporterInterface *iface); +static void dvi_document_do_color_special (DviContext *dvi, + const char *prefix, + const char *arg); + +EV_BACKEND_REGISTER_WITH_CODE (DviDocument, dvi_document, + { + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, dvi_document_document_thumbnails_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_FILE_EXPORTER, dvi_document_file_exporter_iface_init); + }); + +static gboolean +dvi_document_load (EvDocument *document, + const char *uri, + GError **error) +{ + gchar *filename; + DviDocument *dvi_document = DVI_DOCUMENT(document); + + filename = g_filename_from_uri (uri, NULL, error); + if (!filename) + return FALSE; + + g_mutex_lock (dvi_context_mutex); + if (dvi_document->context) + mdvi_destroy_context (dvi_document->context); + + dvi_document->context = mdvi_init_context(dvi_document->params, dvi_document->spec, filename); + g_mutex_unlock (dvi_context_mutex); + g_free (filename); + + if (!dvi_document->context) { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("DVI document has incorrect format")); + return FALSE; + } + + mdvi_cairo_device_init (&dvi_document->context->device); + + + dvi_document->base_width = dvi_document->context->dvi_page_w * dvi_document->context->params.conv + + 2 * unit2pix(dvi_document->params->dpi, MDVI_HMARGIN) / dvi_document->params->hshrink; + + dvi_document->base_height = dvi_document->context->dvi_page_h * dvi_document->context->params.vconv + + 2 * unit2pix(dvi_document->params->vdpi, MDVI_VMARGIN) / dvi_document->params->vshrink; + + g_free (dvi_document->uri); + dvi_document->uri = g_strdup (uri); + + return TRUE; +} + + +static gboolean +dvi_document_save (EvDocument *document, + const char *uri, + GError **error) +{ + DviDocument *dvi_document = DVI_DOCUMENT (document); + + return ev_xfer_uri_simple (dvi_document->uri, uri, error); +} + +static int +dvi_document_get_n_pages (EvDocument *document) +{ + DviDocument *dvi_document = DVI_DOCUMENT (document); + + return dvi_document->context->npages; +} + +static void +dvi_document_get_page_size (EvDocument *document, + EvPage *page, + double *width, + double *height) +{ + DviDocument *dvi_document = DVI_DOCUMENT (document); + + *width = dvi_document->base_width; + *height = dvi_document->base_height;; +} + +static cairo_surface_t * +dvi_document_render (EvDocument *document, + EvRenderContext *rc) +{ + cairo_surface_t *surface; + cairo_surface_t *rotated_surface; + DviDocument *dvi_document = DVI_DOCUMENT(document); + gint required_width, required_height; + gint proposed_width, proposed_height; + gint xmargin = 0, ymargin = 0; + + /* We should protect our context since it's not + * thread safe. The work to the future - + * let context render page independently + */ + g_mutex_lock (dvi_context_mutex); + + mdvi_setpage (dvi_document->context, rc->page->index); + + mdvi_set_shrink (dvi_document->context, + (int)((dvi_document->params->hshrink - 1) / rc->scale) + 1, + (int)((dvi_document->params->vshrink - 1) / rc->scale) + 1); + + required_width = dvi_document->base_width * rc->scale + 0.5; + required_height = dvi_document->base_height * rc->scale + 0.5; + proposed_width = dvi_document->context->dvi_page_w * dvi_document->context->params.conv; + proposed_height = dvi_document->context->dvi_page_h * dvi_document->context->params.vconv; + + if (required_width >= proposed_width) + xmargin = (required_width - proposed_width) / 2; + if (required_height >= proposed_height) + ymargin = (required_height - proposed_height) / 2; + + mdvi_cairo_device_set_margins (&dvi_document->context->device, xmargin, ymargin); + mdvi_cairo_device_set_scale (&dvi_document->context->device, rc->scale); + mdvi_cairo_device_render (dvi_document->context); + surface = mdvi_cairo_device_get_surface (&dvi_document->context->device); + + g_mutex_unlock (dvi_context_mutex); + + rotated_surface = ev_document_misc_surface_rotate_and_scale (surface, + required_width, + required_height, + rc->rotation); + cairo_surface_destroy (surface); + + return rotated_surface; +} + +static void +dvi_document_finalize (GObject *object) +{ + DviDocument *dvi_document = DVI_DOCUMENT(object); + + g_mutex_lock (dvi_context_mutex); + if (dvi_document->context) { + mdvi_cairo_device_free (&dvi_document->context->device); + mdvi_destroy_context (dvi_document->context); + } + g_mutex_unlock (dvi_context_mutex); + + if (dvi_document->params) + g_free (dvi_document->params); + + if (dvi_document->exporter_filename) + g_free (dvi_document->exporter_filename); + + if (dvi_document->exporter_opts) + g_string_free (dvi_document->exporter_opts, TRUE); + + g_free (dvi_document->uri); + + G_OBJECT_CLASS (dvi_document_parent_class)->finalize (object); +} + +static gboolean +dvi_document_support_synctex (EvDocument *document) +{ + return TRUE; +} + +static void +dvi_document_class_init (DviDocumentClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass); + gchar *texmfcnf; + + gobject_class->finalize = dvi_document_finalize; + + texmfcnf = get_texmfcnf(); + mdvi_init_kpathsea ("evince", MDVI_MFMODE, MDVI_FALLBACK_FONT, MDVI_DPI, texmfcnf); + g_free(texmfcnf); + + mdvi_register_special ("Color", "color", NULL, dvi_document_do_color_special, 1); + mdvi_register_fonts (); + + dvi_context_mutex = g_mutex_new (); + + ev_document_class->load = dvi_document_load; + ev_document_class->save = dvi_document_save; + ev_document_class->get_n_pages = dvi_document_get_n_pages; + ev_document_class->get_page_size = dvi_document_get_page_size; + ev_document_class->render = dvi_document_render; + ev_document_class->support_synctex = dvi_document_support_synctex; +} + +static void +dvi_document_thumbnails_get_dimensions (EvDocumentThumbnails *document, + EvRenderContext *rc, + gint *width, + gint *height) +{ + DviDocument *dvi_document = DVI_DOCUMENT (document); + gdouble page_width = dvi_document->base_width; + gdouble page_height = dvi_document->base_height; + + if (rc->rotation == 90 || rc->rotation == 270) { + *width = (gint) (page_height * rc->scale); + *height = (gint) (page_width * rc->scale); + } else { + *width = (gint) (page_width * rc->scale); + *height = (gint) (page_height * rc->scale); + } +} + +static GdkPixbuf * +dvi_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document, + EvRenderContext *rc, + gboolean border) +{ + DviDocument *dvi_document = DVI_DOCUMENT (document); + GdkPixbuf *pixbuf; + GdkPixbuf *rotated_pixbuf; + cairo_surface_t *surface; + gint thumb_width, thumb_height; + gint proposed_width, proposed_height; + + thumb_width = (gint) (dvi_document->base_width * rc->scale); + thumb_height = (gint) (dvi_document->base_height * rc->scale); + + g_mutex_lock (dvi_context_mutex); + + mdvi_setpage (dvi_document->context, rc->page->index); + + mdvi_set_shrink (dvi_document->context, + (int)dvi_document->base_width * dvi_document->params->hshrink / thumb_width, + (int)dvi_document->base_height * dvi_document->params->vshrink / thumb_height); + + proposed_width = dvi_document->context->dvi_page_w * dvi_document->context->params.conv; + proposed_height = dvi_document->context->dvi_page_h * dvi_document->context->params.vconv; + + if (border) { + mdvi_cairo_device_set_margins (&dvi_document->context->device, + MAX (thumb_width - proposed_width, 0) / 2, + MAX (thumb_height - proposed_height, 0) / 2); + } else { + mdvi_cairo_device_set_margins (&dvi_document->context->device, + MAX (thumb_width - proposed_width - 2, 0) / 2, + MAX (thumb_height - proposed_height - 2, 0) / 2); + } + + mdvi_cairo_device_set_scale (&dvi_document->context->device, rc->scale); + mdvi_cairo_device_render (dvi_document->context); + surface = mdvi_cairo_device_get_surface (&dvi_document->context->device); + g_mutex_unlock (dvi_context_mutex); + + pixbuf = ev_document_misc_pixbuf_from_surface (surface); + cairo_surface_destroy (surface); + + rotated_pixbuf = gdk_pixbuf_rotate_simple (pixbuf, 360 - rc->rotation); + g_object_unref (pixbuf); + + if (border) { + GdkPixbuf *tmp_pixbuf = rotated_pixbuf; + + rotated_pixbuf = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf); + g_object_unref (tmp_pixbuf); + } + + return rotated_pixbuf; +} + +static void +dvi_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface) +{ + iface->get_thumbnail = dvi_document_thumbnails_get_thumbnail; + iface->get_dimensions = dvi_document_thumbnails_get_dimensions; +} + +/* EvFileExporterIface */ +static void +dvi_document_file_exporter_begin (EvFileExporter *exporter, + EvFileExporterContext *fc) +{ + DviDocument *dvi_document = DVI_DOCUMENT(exporter); + + if (dvi_document->exporter_filename) + g_free (dvi_document->exporter_filename); + dvi_document->exporter_filename = g_strdup (fc->filename); + + if (dvi_document->exporter_opts) { + g_string_free (dvi_document->exporter_opts, TRUE); + } + dvi_document->exporter_opts = g_string_new ("-s "); +} + +static void +dvi_document_file_exporter_do_page (EvFileExporter *exporter, + EvRenderContext *rc) +{ + DviDocument *dvi_document = DVI_DOCUMENT(exporter); + + g_string_append_printf (dvi_document->exporter_opts, "%d,", (rc->page->index) + 1); +} + +static void +dvi_document_file_exporter_end (EvFileExporter *exporter) +{ + gchar *command_line; + gint exit_stat; + GError *err = NULL; + gboolean success; + + DviDocument *dvi_document = DVI_DOCUMENT(exporter); + + command_line = g_strdup_printf ("dvipdfm %s -o %s \"%s\"", /* dvipdfm -s 1,2,.., -o exporter_filename dvi_filename */ + dvi_document->exporter_opts->str, + dvi_document->exporter_filename, + dvi_document->context->filename); + + success = g_spawn_command_line_sync (command_line, + NULL, + NULL, + &exit_stat, + &err); + + g_free (command_line); + + if (success == FALSE) { + g_warning ("Error: %s", err->message); + } else if (!WIFEXITED(exit_stat) || WEXITSTATUS(exit_stat) != EXIT_SUCCESS){ + g_warning ("Error: dvipdfm does not end normally or exit with a failure status."); + } + + if (err) + g_error_free (err); +} + +static EvFileExporterCapabilities +dvi_document_file_exporter_get_capabilities (EvFileExporter *exporter) +{ + return EV_FILE_EXPORTER_CAN_PAGE_SET | + EV_FILE_EXPORTER_CAN_COPIES | + EV_FILE_EXPORTER_CAN_COLLATE | + EV_FILE_EXPORTER_CAN_REVERSE | + EV_FILE_EXPORTER_CAN_GENERATE_PDF; +} + +static void +dvi_document_file_exporter_iface_init (EvFileExporterInterface *iface) +{ + iface->begin = dvi_document_file_exporter_begin; + iface->do_page = dvi_document_file_exporter_do_page; + iface->end = dvi_document_file_exporter_end; + iface->get_capabilities = dvi_document_file_exporter_get_capabilities; +} + +#define RGB2ULONG(r,g,b) ((0xFF<<24)|(r<<16)|(g<<8)|(b)) + +static gboolean +hsb2rgb (float h, float s, float v, guchar *red, guchar *green, guchar *blue) +{ + float i, f, p, q, t, r, g, b; + + if (h == 360) + h = 0; + else if ((h > 360) || (h < 0)) + return FALSE; + + s /= 100; + v /= 100; + h /= 60; + i = floor (h); + f = h - i; + p = v * (1 - s); + q = v * (1 - (s * f)); + t = v * (1 - (s * (1 - f))); + + if (i == 0) { + r = v; + g = t; + b = p; + } else if (i == 1) { + r = q; + g = v; + b = p; + } else if (i == 2) { + r = p; + g = v; + b = t; + } else if (i == 3) { + r = p; + g = q; + b = v; + } else if (i == 4) { + r = t; + g = p; + b = v; + } else if (i == 5) { + r = v; + g = p; + b = q; + } + + *red = (guchar)floor(r * 255.0); + *green = (guchar)floor(g * 255.0); + *blue = (guchar)floor(b * 255.0); + + return TRUE; +} + +static void +parse_color (const gchar *ptr, + gdouble *color, + gint n_color) +{ + gchar *p = (gchar *)ptr; + gint i; + + for (i = 0; i < n_color; i++) { + while (isspace (*p)) p++; + color[i] = g_ascii_strtod (p, NULL); + while (!isspace (*p) && *p != '\0') p++; + if (*p == '\0') + break; + } +} + +static void +dvi_document_do_color_special (DviContext *dvi, const char *prefix, const char *arg) +{ + if (strncmp (arg, "pop", 3) == 0) { + mdvi_pop_color (dvi); + } else if (strncmp (arg, "push", 4) == 0) { + /* Find color source: Named, CMYK or RGB */ + const char *tmp = arg + 4; + + while (isspace (*tmp)) tmp++; + + if (!strncmp ("rgb", tmp, 3)) { + gdouble rgb[3]; + guchar red, green, blue; + + parse_color (tmp + 4, rgb, 3); + + red = 255 * rgb[0]; + green = 255 * rgb[1]; + blue = 255 * rgb[2]; + + mdvi_push_color (dvi, RGB2ULONG (red, green, blue), 0xFFFFFFFF); + } else if (!strncmp ("hsb", tmp, 4)) { + gdouble hsb[3]; + guchar red, green, blue; + + parse_color (tmp + 4, hsb, 3); + + if (hsb2rgb (hsb[0], hsb[1], hsb[2], &red, &green, &blue)) + mdvi_push_color (dvi, RGB2ULONG (red, green, blue), 0xFFFFFFFF); + } else if (!strncmp ("cmyk", tmp, 4)) { + gdouble cmyk[4]; + double r, g, b; + guchar red, green, blue; + + parse_color (tmp + 5, cmyk, 4); + + r = 1.0 - cmyk[0] - cmyk[3]; + if (r < 0.0) + r = 0.0; + g = 1.0 - cmyk[1] - cmyk[3]; + if (g < 0.0) + g = 0.0; + b = 1.0 - cmyk[2] - cmyk[3]; + if (b < 0.0) + b = 0.0; + + red = r * 255 + 0.5; + green = g * 255 + 0.5; + blue = b * 255 + 0.5; + + mdvi_push_color (dvi, RGB2ULONG (red, green, blue), 0xFFFFFFFF); + } else if (!strncmp ("gray ", tmp, 5)) { + gdouble gray; + guchar rgb; + + parse_color (tmp + 5, &gray, 1); + + rgb = gray * 255 + 0.5; + + mdvi_push_color (dvi, RGB2ULONG (rgb, rgb, rgb), 0xFFFFFFFF); + } else { + GdkColor color; + + if (gdk_color_parse (tmp, &color)) { + guchar red, green, blue; + + red = color.red * 255 / 65535.; + green = color.green * 255 / 65535.; + blue = color.blue * 255 / 65535.; + + mdvi_push_color (dvi, RGB2ULONG (red, green, blue), 0xFFFFFFFF); + } + } + } +} + +static void +dvi_document_init_params (DviDocument *dvi_document) +{ + dvi_document->params = g_new0 (DviParams, 1); + + dvi_document->params->dpi = MDVI_DPI; + dvi_document->params->vdpi = MDVI_VDPI; + dvi_document->params->mag = MDVI_MAGNIFICATION; + dvi_document->params->density = MDVI_DEFAULT_DENSITY; + dvi_document->params->gamma = MDVI_DEFAULT_GAMMA; + dvi_document->params->flags = MDVI_PARAM_ANTIALIASED; + dvi_document->params->hdrift = 0; + dvi_document->params->vdrift = 0; + dvi_document->params->hshrink = MDVI_SHRINK_FROM_DPI(dvi_document->params->dpi); + dvi_document->params->vshrink = MDVI_SHRINK_FROM_DPI(dvi_document->params->vdpi); + dvi_document->params->orientation = MDVI_ORIENT_TBLR; + + dvi_document->spec = NULL; + + dvi_document->params->bg = 0xffffffff; + dvi_document->params->fg = 0xff000000; +} + +static void +dvi_document_init (DviDocument *dvi_document) +{ + dvi_document->context = NULL; + dvi_document_init_params (dvi_document); + + dvi_document->exporter_filename = NULL; + dvi_document->exporter_opts = NULL; +} diff --git a/backend/dvi/dvi-document.h b/backend/dvi/dvi-document.h new file mode 100644 index 00000000..845301b6 --- /dev/null +++ b/backend/dvi/dvi-document.h @@ -0,0 +1,38 @@ +/* dvi-document.h: Implementation of EvDocument for dvi documents + * Copyright (C) 2005, Nickolay V. Shmyrev + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __DVI_DOCUMENT_H__ +#define __DVI_DOCUMENT_H__ + +#include "ev-document.h" + +G_BEGIN_DECLS + +#define DVI_TYPE_DOCUMENT (dvi_document_get_type ()) +#define DVI_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DVI_TYPE_DOCUMENT, DviDocument)) +#define DVI_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DVI_TYPE_DOCUMENT)) + +typedef struct _DviDocument DviDocument; + +GType dvi_document_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT GType register_evince_backend (GTypeModule *module); + +G_END_DECLS + +#endif /* __DVI_DOCUMENT_H__ */ diff --git a/backend/dvi/dvidocument.evince-backend.in b/backend/dvi/dvidocument.evince-backend.in new file mode 100644 index 00000000..cb5fe6ed --- /dev/null +++ b/backend/dvi/dvidocument.evince-backend.in @@ -0,0 +1,4 @@ +[Evince Backend] +Module=dvidocument +_TypeDescription=DVI Documents +MimeType=application/x-dvi;application/x-bzdvi;application/x-gzdvi diff --git a/backend/dvi/fonts.c b/backend/dvi/fonts.c new file mode 100644 index 00000000..99be63ce --- /dev/null +++ b/backend/dvi/fonts.c @@ -0,0 +1,57 @@ +#include "config.h" +#include "fonts.h" +#include "mdvi.h" + +static int registered = 0; + +extern DviFontInfo pk_font_info; +extern DviFontInfo pkn_font_info; +extern DviFontInfo gf_font_info; +extern DviFontInfo vf_font_info; +extern DviFontInfo ovf_font_info; +#if 0 +extern DviFontInfo tt_font_info; +#endif +#ifdef WITH_TYPE1_FONTS +extern DviFontInfo t1_font_info; +#endif +extern DviFontInfo afm_font_info; +extern DviFontInfo tfm_font_info; +extern DviFontInfo ofm_font_info; + +static struct fontinfo { + DviFontInfo *info; + char *desc; + int klass; +} known_fonts[] = { + {&vf_font_info, "Virtual fonts", 0}, + {&ovf_font_info, "Omega's virtual fonts", 0}, +#if 0 + {&tt_font_info, "TrueType fonts", 0}, +#endif +#ifdef WITH_TYPE1_FONTS + {&t1_font_info, "Type1 PostScript fonts", 0}, +#endif + {&pk_font_info, "Packed bitmap (auto-generated)", 1}, + {&pkn_font_info, "Packed bitmap", -2}, + {&gf_font_info, "Metafont's generic font format", 1}, + {&ofm_font_info, "Omega font metrics", -1}, + {&tfm_font_info, "TeX font metrics", -1}, + {&afm_font_info, "Adobe font metrics", -1}, + {0, 0} +}; + +void mdvi_register_fonts (void) +{ + struct fontinfo *type; + + if (!registered) { + for(type = known_fonts; type->info; type++) { + mdvi_register_font_type(type->info, type->klass); + } + registered = 1; + } + return; +} + + diff --git a/backend/dvi/fonts.h b/backend/dvi/fonts.h new file mode 100644 index 00000000..e55a8ddf --- /dev/null +++ b/backend/dvi/fonts.h @@ -0,0 +1,6 @@ +#ifndef MDVI_FONTS_REGISTRATION_H +#define MDVI_FONTS_REGISTRATION_H + +void mdvi_register_fonts (void); + +#endif /* MDVI_FONTS_REGISTRATION_H */ diff --git a/backend/dvi/mdvi-lib/Makefile.am b/backend/dvi/mdvi-lib/Makefile.am new file mode 100644 index 00000000..e976aa60 --- /dev/null +++ b/backend/dvi/mdvi-lib/Makefile.am @@ -0,0 +1,43 @@ +INCLUDES = $(WARN_CFLAGS) +noinst_LTLIBRARIES = libmdvi.la + +libmdvi_la_SOURCES = \ + afmparse.c \ + afmparse.h \ + bitmap.c \ + bitmap.h \ + color.c \ + color.h \ + common.c \ + common.h \ + defaults.h \ + dviopcodes.h \ + dviread.c \ + files.c \ + font.c \ + fontmap.c \ + fontmap.h \ + fontsrch.c \ + gf.c \ + hash.c \ + hash.h \ + list.c \ + mdvi.h \ + pagesel.c \ + paper.c \ + paper.h \ + pk.c \ + private.h \ + setup.c \ + special.c \ + sp-epsf.c \ + sysdeps.h \ + t1.c \ + tfm.c \ + tfmfile.c \ + tt.c \ + util.c \ + vf.c + + +-include $(top_srcdir)/git.mk diff --git a/backend/dvi/mdvi-lib/afmparse.c b/backend/dvi/mdvi-lib/afmparse.c new file mode 100644 index 00000000..164366b0 --- /dev/null +++ b/backend/dvi/mdvi-lib/afmparse.c @@ -0,0 +1,1303 @@ +/* + * (C) 1988, 1989, 1990 by Adobe Systems Incorporated. All rights reserved. + * + * This file may be freely copied and redistributed as long as: + * 1) This entire notice continues to be included in the file, + * 2) If the file has been modified in any way, a notice of such + * modification is conspicuously indicated. + * + * PostScript, Display PostScript, and Adobe are registered trademarks of + * Adobe Systems Incorporated. + * + * ************************************************************************ + * THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO CHANGE WITHOUT + * NOTICE, AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY ADOBE SYSTEMS + * INCORPORATED. ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY OR + * LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO WARRANTY OF ANY + * KIND (EXPRESS, IMPLIED OR STATUTORY) WITH RESPECT TO THIS INFORMATION, + * AND EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * ************************************************************************ + */ + +/* + * modified for MDVI: + * - some names changed to avoid conflicts with T1lib + * - changed to ANSI C prototypes, as used by MDVI + * mal - 3/01 + */ + +/* parseAFM.c + * + * This file is used in conjuction with the parseAFM.h header file. + * This file contains several procedures that are used to parse AFM + * files. It is intended to work with an application program that needs + * font metric information. The program can be used as is by making a + * procedure call to "parseFile" (passing in the expected parameters) + * and having it fill in a data structure with the data from the + * AFM file, or an application developer may wish to customize this + * code. + * + * There is also a file, parseAFMclient.c, that is a sample application + * showing how to call the "parseFile" procedure and how to use the data + * after "parseFile" has returned. + * + * Please read the comments in parseAFM.h and parseAFMclient.c. + * + * History: + * original: DSM Thu Oct 20 17:39:59 PDT 1988 + * modified: DSM Mon Jul 3 14:17:50 PDT 1989 + * - added 'storageProblem' return code + * - fixed bug of not allocating extra byte for string duplication + * - fixed typos + * modified: DSM Tue Apr 3 11:18:34 PDT 1990 + * - added free(ident) at end of parseFile routine + * modified: DSM Tue Jun 19 10:16:29 PDT 1990 + * - changed (width == 250) to (width = 250) in initializeArray + */ + +#include +#include "sysdeps.h" + +#ifdef WITH_AFM_FILES + +#include /* added for MDVI */ +#include /* added for MDVI */ + +#include +#include +#include +#include +#include "afmparse.h" +#undef VERSION + +#define lineterm EOL /* line terminating character */ +#define normalEOF 1 /* return code from parsing routines used only */ + /* in this module */ +#define Space "space" /* used in string comparison to look for the width */ + /* of the space character to init the widths array */ +#define False "false" /* used in string comparison to check the value of */ + /* boolean keys (e.g. IsFixedPitch) */ + +#define MATCH(A,B) (strncmp((A),(B), MAX_NAME) == 0) + + + +/*************************** GLOBALS ***********************/ + +static char *ident = NULL; /* storage buffer for keywords */ + + +/* "shorts" for fast case statement + * The values of each of these enumerated items correspond to an entry in the + * table of strings defined below. Therefore, if you add a new string as + * new keyword into the keyStrings table, you must also add a corresponding + * parseKey AND it MUST be in the same position! + * + * IMPORTANT: since the sorting algorithm is a binary search, the strings of + * keywords must be placed in lexicographical order, below. [Therefore, the + * enumerated items are not necessarily in lexicographical order, depending + * on the name chosen. BUT, they must be placed in the same position as the + * corresponding key string.] The NOPE shall remain in the last position, + * since it does not correspond to any key string, and it is used in the + * "recognize" procedure to calculate how many possible keys there are. + */ + +enum parseKey { + ASCENDER, CHARBBOX, CODE, COMPCHAR, CAPHEIGHT, COMMENT, + DESCENDER, ENCODINGSCHEME, ENDCHARMETRICS, ENDCOMPOSITES, + ENDFONTMETRICS, ENDKERNDATA, ENDKERNPAIRS, ENDTRACKKERN, + FAMILYNAME, FONTBBOX, FONTNAME, FULLNAME, ISFIXEDPITCH, + ITALICANGLE, KERNPAIR, KERNPAIRXAMT, LIGATURE, CHARNAME, + NOTICE, COMPCHARPIECE, STARTCHARMETRICS, STARTCOMPOSITES, + STARTFONTMETRICS, STARTKERNDATA, STARTKERNPAIRS, + STARTTRACKKERN, TRACKKERN, UNDERLINEPOSITION, + UNDERLINETHICKNESS, VERSION, XYWIDTH, XWIDTH, WEIGHT, XHEIGHT, + NOPE } ; + +/* keywords for the system: + * This a table of all of the current strings that are vaild AFM keys. + * Each entry can be referenced by the appropriate parseKey value (an + * enumerated data type defined above). If you add a new keyword here, + * a corresponding parseKey MUST be added to the enumerated data type + * defined above, AND it MUST be added in the same position as the + * string is in this table. + * + * IMPORTANT: since the sorting algorithm is a binary search, the keywords + * must be placed in lexicographical order. And, NULL should remain at the + * end. + */ + +static char *keyStrings[] = { + "Ascender", "B", "C", "CC", "CapHeight", "Comment", + "Descender", "EncodingScheme", "EndCharMetrics", "EndComposites", + "EndFontMetrics", "EndKernData", "EndKernPairs", "EndTrackKern", + "FamilyName", "FontBBox", "FontName", "FullName", "IsFixedPitch", + "ItalicAngle", "KP", "KPX", "L", "N", + "Notice", "PCC", "StartCharMetrics", "StartComposites", + "StartFontMetrics", "StartKernData", "StartKernPairs", + "StartTrackKern", "TrackKern", "UnderlinePosition", + "UnderlineThickness", "Version", "W", "WX", "Weight", "XHeight", + NULL }; + +/*************************** PARSING ROUTINES **************/ + +/*************************** token *************************/ + +/* A "AFM File Conventions" tokenizer. That means that it will + * return the next token delimited by white space. See also + * the `linetoken' routine, which does a similar thing but + * reads all tokens until the next end-of-line. + */ + +static char *token(FILE *stream) +{ + int ch, idx; + + /* skip over white space */ + while ((ch = fgetc(stream)) == ' ' || ch == lineterm || + ch == ',' || ch == '\t' || ch == ';'); + + idx = 0; + while (ch != EOF && ch != ' ' && ch != lineterm + && ch != '\t' && ch != ':' && ch != ';') + { + ident[idx++] = ch; + ch = fgetc(stream); + } /* while */ + + if (ch == EOF && idx < 1) return ((char *)NULL); + if (idx >= 1 && ch != ':' ) ungetc(ch, stream); + if (idx < 1 ) ident[idx++] = ch; /* single-character token */ + ident[idx] = 0; + + return(ident); /* returns pointer to the token */ + +} /* token */ + + +/*************************** linetoken *************************/ + +/* "linetoken" will get read all tokens until the EOL character from + * the given stream. This is used to get any arguments that can be + * more than one word (like Comment lines and FullName). + */ + +static char *linetoken(FILE *stream) +{ + int ch, idx; + + while ((ch = fgetc(stream)) == ' ' || ch == '\t' ); + + idx = 0; + while (ch != EOF && ch != lineterm) + { + ident[idx++] = ch; + ch = fgetc(stream); + } /* while */ + + ungetc(ch, stream); + ident[idx] = 0; + + return(ident); /* returns pointer to the token */ + +} /* linetoken */ + + +/*************************** recognize *************************/ + +/* This function tries to match a string to a known list of + * valid AFM entries (check the keyStrings array above). + * "ident" contains everything from white space through the + * next space, tab, or ":" character. + * + * The algorithm is a standard Knuth binary search. + */ + +static enum parseKey recognize(char *ident) +{ + int lower = 0, upper = (int) NOPE, midpoint, cmpvalue; + BOOL found = FALSE; + + while ((upper >= lower) && !found) + { + midpoint = (lower + upper)/2; + if (keyStrings[midpoint] == NULL) break; + cmpvalue = strncmp(ident, keyStrings[midpoint], MAX_NAME); + if (cmpvalue == 0) found = TRUE; + else if (cmpvalue < 0) upper = midpoint - 1; + else lower = midpoint + 1; + } /* while */ + + if (found) return (enum parseKey) midpoint; + else return NOPE; + +} /* recognize */ + + +/************************* parseGlobals *****************************/ + +/* This function is called by "parseFile". It will parse the AFM File + * up to the "StartCharMetrics" keyword, which essentially marks the + * end of the Global Font Information and the beginning of the character + * metrics information. + * + * If the caller of "parseFile" specified that it wanted the Global + * Font Information (as defined by the "AFM File Specification" + * document), then that information will be stored in the returned + * data structure. + * + * Any Global Font Information entries that are not found in a + * given file, will have the usual default initialization value + * for its type (i.e. entries of type int will be 0, etc). + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static BOOL parseGlobals(FILE *fp, GlobalFontInfo *gfi) +{ + BOOL cont = TRUE, save = (gfi != NULL); + int error = ok; + register char *keyword; + + while (cont) + { + keyword = token(fp); + + if (keyword == NULL) + /* Have reached an early and unexpected EOF. */ + /* Set flag and stop parsing */ + { + error = earlyEOF; + break; /* get out of loop */ + } + if (!save) + /* get tokens until the end of the Global Font info section */ + /* without saving any of the data */ + switch (recognize(keyword)) + { + case STARTCHARMETRICS: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + default: + break; + } /* switch */ + else + /* otherwise parse entire global font info section, */ + /* saving the data */ + switch(recognize(keyword)) + { + case STARTFONTMETRICS: + keyword = token(fp); + gfi->afmVersion = (char *) malloc(strlen(keyword) + 1); + strcpy(gfi->afmVersion, keyword); + break; + case COMMENT: + keyword = linetoken(fp); + break; + case FONTNAME: + keyword = token(fp); + gfi->fontName = (char *) malloc(strlen(keyword) + 1); + strcpy(gfi->fontName, keyword); + break; + case ENCODINGSCHEME: + keyword = token(fp); + gfi->encodingScheme = (char *) + malloc(strlen(keyword) + 1); + strcpy(gfi->encodingScheme, keyword); + break; + case FULLNAME: + keyword = linetoken(fp); + gfi->fullName = (char *) malloc(strlen(keyword) + 1); + strcpy(gfi->fullName, keyword); + break; + case FAMILYNAME: + keyword = linetoken(fp); + gfi->familyName = (char *) malloc(strlen(keyword) + 1); + strcpy(gfi->familyName, keyword); + break; + case WEIGHT: + keyword = token(fp); + gfi->weight = (char *) malloc(strlen(keyword) + 1); + strcpy(gfi->weight, keyword); + break; + case ITALICANGLE: + keyword = token(fp); + gfi->italicAngle = atof(keyword); + if (errno == ERANGE) error = parseError; + break; + case ISFIXEDPITCH: + keyword = token(fp); + if (MATCH(keyword, False)) + gfi->isFixedPitch = 0; + else + gfi->isFixedPitch = 1; + break; + case UNDERLINEPOSITION: + keyword = token(fp); + gfi->underlinePosition = atoi(keyword); + break; + case UNDERLINETHICKNESS: + keyword = token(fp); + gfi->underlineThickness = atoi(keyword); + break; + case VERSION: + keyword = token(fp); + gfi->version = (char *) malloc(strlen(keyword) + 1); + strcpy(gfi->version, keyword); + break; + case NOTICE: + keyword = linetoken(fp); + gfi->notice = (char *) malloc(strlen(keyword) + 1); + strcpy(gfi->notice, keyword); + break; + case FONTBBOX: + keyword = token(fp); + gfi->fontBBox.llx = atoi(keyword); + keyword = token(fp); + gfi->fontBBox.lly = atoi(keyword); + keyword = token(fp); + gfi->fontBBox.urx = atoi(keyword); + keyword = token(fp); + gfi->fontBBox.ury = atoi(keyword); + break; + case CAPHEIGHT: + keyword = token(fp); + gfi->capHeight = atoi(keyword); + break; + case XHEIGHT: + keyword = token(fp); + gfi->xHeight = atoi(keyword); + break; + case DESCENDER: + keyword = token(fp); + gfi->descender = atoi(keyword); + break; + case ASCENDER: + keyword = token(fp); + gfi->ascender = atoi(keyword); + break; + case STARTCHARMETRICS: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + return(error); + +} /* parseGlobals */ + + + +#if 0 /* this function does not seem to be used anywhere */ +/************************* initializeArray ************************/ + +/* Unmapped character codes are (at Adobe Systems) assigned the + * width of the space character (if one exists) else they get the + * value of 250 ems. This function initializes all entries in the + * char widths array to have this value. Then any mapped character + * codes will be replaced with the width of the appropriate character + * when parsing the character metric section. + + * This function parses the Character Metrics Section looking + * for a space character (by comparing character names). If found, + * the width of the space character will be used to initialize the + * values in the array of character widths. + * + * Before returning, the position of the read/write pointer of the + * file is reset to be where it was upon entering this function. + */ + +static int initializeArray(FILE *fp, int *cwi) +{ + BOOL cont = TRUE, found = FALSE; + long opos = ftell(fp); + int code = 0, width = 0, i = 0, error = 0; + register char *keyword; + + while (cont) + { + keyword = token(fp); + if (keyword == NULL) + { + error = earlyEOF; + break; /* get out of loop */ + } + switch(recognize(keyword)) + { + case COMMENT: + keyword = linetoken(fp); + break; + case CODE: + code = atoi(token(fp)); + break; + case XWIDTH: + width = atoi(token(fp)); + break; + case CHARNAME: + keyword = token(fp); + if (MATCH(keyword, Space)) + { + cont = FALSE; + found = TRUE; + } + break; + case ENDCHARMETRICS: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + if (!found) + width = 250; + + for (i = 0; i < 256; ++i) + cwi[i] = width; + + fseek(fp, opos, 0); + + return(error); + +} /* initializeArray */ +#endif /* unused */ + +/************************* parseCharWidths **************************/ + +/* This function is called by "parseFile". It will parse the AFM File + * up to the "EndCharMetrics" keyword. It will save the character + * width info (as opposed to all of the character metric information) + * if requested by the caller of parseFile. Otherwise, it will just + * parse through the section without saving any information. + * + * If data is to be saved, parseCharWidths is passed in a pointer + * to an array of widths that has already been initialized by the + * standard value for unmapped character codes. This function parses + * the Character Metrics section only storing the width information + * for the encoded characters into the array using the character code + * as the index into that array. + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parseCharWidths(FILE *fp, int *cwi) +{ + BOOL cont = TRUE, save = (cwi != NULL); + int pos = 0, error = ok; + register char *keyword; + + while (cont) + { + keyword = token(fp); + /* Have reached an early and unexpected EOF. */ + /* Set flag and stop parsing */ + if (keyword == NULL) + { + error = earlyEOF; + break; /* get out of loop */ + } + if (!save) + /* get tokens until the end of the Char Metrics section without */ + /* saving any of the data*/ + switch (recognize(keyword)) + { + case ENDCHARMETRICS: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + default: + break; + } /* switch */ + else + /* otherwise parse entire char metrics section, saving */ + /* only the char x-width info */ + switch(recognize(keyword)) + { + case COMMENT: + keyword = linetoken(fp); + break; + case CODE: + keyword = token(fp); + pos = atoi(keyword); + break; + case XYWIDTH: + /* PROBLEM: Should be no Y-WIDTH when doing "quick & dirty" */ + keyword = token(fp); keyword = token(fp); /* eat values */ + error = parseError; + break; + case XWIDTH: + keyword = token(fp); + if (pos >= 0) /* ignore unmapped chars */ + cwi[pos] = atoi(keyword); + break; + case ENDCHARMETRICS: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + case CHARNAME: /* eat values (so doesn't cause parseError) */ + keyword = token(fp); + break; + case CHARBBOX: + keyword = token(fp); keyword = token(fp); + keyword = token(fp); keyword = token(fp); + break; + case LIGATURE: + keyword = token(fp); keyword = token(fp); + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + return(error); + +} /* parseCharWidths */ + + +/************************* parseCharMetrics ************************/ + +/* This function is called by parseFile if the caller of parseFile + * requested that all character metric information be saved + * (as opposed to only the character width information). + * + * parseCharMetrics is passed in a pointer to an array of records + * to hold information on a per character basis. This function + * parses the Character Metrics section storing all character + * metric information for the ALL characters (mapped and unmapped) + * into the array. + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parseCharMetrics(FILE *fp, FontInfo *fi) +{ + BOOL cont = TRUE, firstTime = TRUE; + int error = ok, count = 0; + register CharMetricInfo *temp = fi->cmi; + register char *keyword; + + while (cont) + { + keyword = token(fp); + if (keyword == NULL) + { + error = earlyEOF; + break; /* get out of loop */ + } + switch(recognize(keyword)) + { + case COMMENT: + keyword = linetoken(fp); + break; + case CODE: + if (count < fi->numOfChars) + { + if (firstTime) firstTime = FALSE; + else temp++; + temp->code = atoi(token(fp)); + count++; + } + else + { + error = parseError; + cont = FALSE; + } + break; + case XYWIDTH: + temp->wx = atoi(token(fp)); + temp->wy = atoi(token(fp)); + break; + case XWIDTH: + temp->wx = atoi(token(fp)); + break; + case CHARNAME: + keyword = token(fp); + temp->name = (char *) malloc(strlen(keyword) + 1); + strcpy(temp->name, keyword); + break; + case CHARBBOX: + temp->charBBox.llx = atoi(token(fp)); + temp->charBBox.lly = atoi(token(fp)); + temp->charBBox.urx = atoi(token(fp)); + temp->charBBox.ury = atoi(token(fp)); + break; + case LIGATURE: { + Ligature **tail = &(temp->ligs); + Ligature *node = *tail; + + if (*tail != NULL) + { + while (node->next != NULL) + node = node->next; + tail = &(node->next); + } + + *tail = (Ligature *) calloc(1, sizeof(Ligature)); + keyword = token(fp); + (*tail)->succ = (char *) malloc(strlen(keyword) + 1); + strcpy((*tail)->succ, keyword); + keyword = token(fp); + (*tail)->lig = (char *) malloc(strlen(keyword) + 1); + strcpy((*tail)->lig, keyword); + break; } + case ENDCHARMETRICS: + cont = FALSE;; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + if ((error == ok) && (count != fi->numOfChars)) + error = parseError; + + return(error); + +} /* parseCharMetrics */ + + + +/************************* parseTrackKernData ***********************/ + +/* This function is called by "parseFile". It will parse the AFM File + * up to the "EndTrackKern" or "EndKernData" keywords. It will save the + * track kerning data if requested by the caller of parseFile. + * + * parseTrackKernData is passed in a pointer to the FontInfo record. + * If data is to be saved, the FontInfo record will already contain + * a valid pointer to storage for the track kerning data. + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parseTrackKernData(FILE *fp, FontInfo *fi) +{ + BOOL cont = TRUE, save = (fi->tkd != NULL); + int pos = 0, error = ok, tcount = 0; + register char *keyword; + + while (cont) + { + keyword = token(fp); + + if (keyword == NULL) + { + error = earlyEOF; + break; /* get out of loop */ + } + if (!save) + /* get tokens until the end of the Track Kerning Data */ + /* section without saving any of the data */ + switch(recognize(keyword)) + { + case ENDTRACKKERN: + case ENDKERNDATA: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + default: + break; + } /* switch */ + else + /* otherwise parse entire Track Kerning Data section, */ + /* saving the data */ + switch(recognize(keyword)) + { + case COMMENT: + keyword = linetoken(fp); + break; + case TRACKKERN: + if (tcount < fi->numOfTracks) + { + keyword = token(fp); + fi->tkd[pos].degree = atoi(keyword); + keyword = token(fp); + fi->tkd[pos].minPtSize = atof(keyword); + if (errno == ERANGE) error = parseError; + keyword = token(fp); + fi->tkd[pos].minKernAmt = atof(keyword); + if (errno == ERANGE) error = parseError; + keyword = token(fp); + fi->tkd[pos].maxPtSize = atof(keyword); + if (errno == ERANGE) error = parseError; + keyword = token(fp); + fi->tkd[pos++].maxKernAmt = atof(keyword); + if (errno == ERANGE) error = parseError; + tcount++; + } + else + { + error = parseError; + cont = FALSE; + } + break; + case ENDTRACKKERN: + case ENDKERNDATA: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + if (error == ok && tcount != fi->numOfTracks) + error = parseError; + + return(error); + +} /* parseTrackKernData */ + + +/************************* parsePairKernData ************************/ + +/* This function is called by "parseFile". It will parse the AFM File + * up to the "EndKernPairs" or "EndKernData" keywords. It will save + * the pair kerning data if requested by the caller of parseFile. + * + * parsePairKernData is passed in a pointer to the FontInfo record. + * If data is to be saved, the FontInfo record will already contain + * a valid pointer to storage for the pair kerning data. + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parsePairKernData(FILE *fp, FontInfo *fi) +{ + BOOL cont = TRUE, save = (fi->pkd != NULL); + int pos = 0, error = ok, pcount = 0; + register char *keyword; + + while (cont) + { + keyword = token(fp); + + if (keyword == NULL) + { + error = earlyEOF; + break; /* get out of loop */ + } + if (!save) + /* get tokens until the end of the Pair Kerning Data */ + /* section without saving any of the data */ + switch(recognize(keyword)) + { + case ENDKERNPAIRS: + case ENDKERNDATA: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + default: + break; + } /* switch */ + else + /* otherwise parse entire Pair Kerning Data section, */ + /* saving the data */ + switch(recognize(keyword)) + { + case COMMENT: + keyword = linetoken(fp); + break; + case KERNPAIR: + if (pcount < fi->numOfPairs) + { + keyword = token(fp); + fi->pkd[pos].name1 = (char *) + malloc(strlen(keyword) + 1); + strcpy(fi->pkd[pos].name1, keyword); + keyword = token(fp); + fi->pkd[pos].name2 = (char *) + malloc(strlen(keyword) + 1); + strcpy(fi->pkd[pos].name2, keyword); + keyword = token(fp); + fi->pkd[pos].xamt = atoi(keyword); + keyword = token(fp); + fi->pkd[pos++].yamt = atoi(keyword); + pcount++; + } + else + { + error = parseError; + cont = FALSE; + } + break; + case KERNPAIRXAMT: + if (pcount < fi->numOfPairs) + { + keyword = token(fp); + fi->pkd[pos].name1 = (char *) + malloc(strlen(keyword) + 1); + strcpy(fi->pkd[pos].name1, keyword); + keyword = token(fp); + fi->pkd[pos].name2 = (char *) + malloc(strlen(keyword) + 1); + strcpy(fi->pkd[pos].name2, keyword); + keyword = token(fp); + fi->pkd[pos++].xamt = atoi(keyword); + pcount++; + } + else + { + error = parseError; + cont = FALSE; + } + break; + case ENDKERNPAIRS: + case ENDKERNDATA: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + if (error == ok && pcount != fi->numOfPairs) + error = parseError; + + return(error); + +} /* parsePairKernData */ + + +/************************* parseCompCharData **************************/ + +/* This function is called by "parseFile". It will parse the AFM File + * up to the "EndComposites" keyword. It will save the composite + * character data if requested by the caller of parseFile. + * + * parseCompCharData is passed in a pointer to the FontInfo record, and + * a boolean representing if the data should be saved. + * + * This function will create the appropriate amount of storage for + * the composite character data and store a pointer to the storage + * in the FontInfo record. + * + * This function returns an error code specifying whether there was + * a premature EOF or a parsing error. This return value is used by + * parseFile to determine if there is more file to parse. + */ + +static int parseCompCharData(FILE *fp, FontInfo *fi) +{ + BOOL cont = TRUE, firstTime = TRUE, save = (fi->ccd != NULL); + int pos = 0, j = 0, error = ok, ccount = 0, pcount = 0; + register char *keyword; + + while (cont) + { + keyword = token(fp); + if (keyword == NULL) + /* Have reached an early and unexpected EOF. */ + /* Set flag and stop parsing */ + { + error = earlyEOF; + break; /* get out of loop */ + } + if (ccount > fi->numOfComps) + { + error = parseError; + break; /* get out of loop */ + } + if (!save) + /* get tokens until the end of the Composite Character info */ + /* section without saving any of the data */ + switch(recognize(keyword)) + { + case ENDCOMPOSITES: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + default: + break; + } /* switch */ + else + /* otherwise parse entire Composite Character info section, */ + /* saving the data */ + switch(recognize(keyword)) + { + case COMMENT: + keyword = linetoken(fp); + break; + case COMPCHAR: + if (ccount < fi->numOfComps) + { + keyword = token(fp); + if (pcount != fi->ccd[pos].numOfPieces) + error = parseError; + pcount = 0; + if (firstTime) firstTime = FALSE; + else pos++; + fi->ccd[pos].ccName = (char *) + malloc(strlen(keyword) + 1); + strcpy(fi->ccd[pos].ccName, keyword); + keyword = token(fp); + fi->ccd[pos].numOfPieces = atoi(keyword); + fi->ccd[pos].pieces = (Pcc *) + calloc(fi->ccd[pos].numOfPieces, sizeof(Pcc)); + j = 0; + ccount++; + } + else + { + error = parseError; + cont = FALSE; + } + break; + case COMPCHARPIECE: + if (pcount < fi->ccd[pos].numOfPieces) + { + keyword = token(fp); + fi->ccd[pos].pieces[j].pccName = (char *) + malloc(strlen(keyword) + 1); + strcpy(fi->ccd[pos].pieces[j].pccName, keyword); + keyword = token(fp); + fi->ccd[pos].pieces[j].deltax = atoi(keyword); + keyword = token(fp); + fi->ccd[pos].pieces[j++].deltay = atoi(keyword); + pcount++; + } + else + error = parseError; + break; + case ENDCOMPOSITES: + cont = FALSE; + break; + case ENDFONTMETRICS: + cont = FALSE; + error = normalEOF; + break; + case NOPE: + default: + error = parseError; + break; + } /* switch */ + } /* while */ + + if (error == ok && ccount != fi->numOfComps) + error = parseError; + + return(error); + +} /* parseCompCharData */ + + + + +/*************************** 'PUBLIC' FUNCTION ********************/ + + +/*************************** parseFile *****************************/ + +/* parseFile is the only 'public' procedure available. It is called + * from an application wishing to get information from an AFM file. + * The caller of this function is responsible for locating and opening + * an AFM file and handling all errors associated with that task. + * + * parseFile expects 3 parameters: a vaild file pointer, a pointer + * to a (FontInfo *) variable (for which storage will be allocated and + * the data requested filled in), and a mask specifying which + * data from the AFM File should be saved in the FontInfo structure. + * + * The file will be parsed and the requested data will be stored in + * a record of type FontInfo (refer to ParseAFM.h). + * + * parseFile returns an error code as defined in parseAFM.h. + * + * The position of the read/write pointer associated with the file + * pointer upon return of this function is undefined. + */ + +extern int afm_parse_file(FILE *fp, FontInfo **fi, FLAGS flags) +{ + + int code = ok; /* return code from each of the parsing routines */ + int error = ok; /* used as the return code from this function */ + + register char *keyword; /* used to store a token */ + + + /* storage data for the global variable ident */ + ident = (char *) calloc(MAX_NAME, sizeof(char)); + if (ident == NULL) {error = storageProblem; return(error);} + + (*fi) = (FontInfo *) calloc(1, sizeof(FontInfo)); + if ((*fi) == NULL) {error = storageProblem; return(error);} + + if (flags & P_G) + { + (*fi)->gfi = (GlobalFontInfo *) calloc(1, sizeof(GlobalFontInfo)); + if ((*fi)->gfi == NULL) {error = storageProblem; return(error);} + } + + /* The AFM File begins with Global Font Information. This section */ + /* will be parsed whether or not information should be saved. */ + code = parseGlobals(fp, (*fi)->gfi); + + if (code < 0) error = code; + + /* The Global Font Information is followed by the Character Metrics */ + /* section. Which procedure is used to parse this section depends on */ + /* how much information should be saved. If all of the metrics info */ + /* is wanted, parseCharMetrics is called. If only the character widths */ + /* is wanted, parseCharWidths is called. parseCharWidths will also */ + /* be called in the case that no character data is to be saved, just */ + /* to parse through the section. */ + + if ((code != normalEOF) && (code != earlyEOF)) + { + (*fi)->numOfChars = atoi(token(fp)); + if (flags & (P_M ^ P_W)) + { + (*fi)->cmi = (CharMetricInfo *) + calloc((*fi)->numOfChars, sizeof(CharMetricInfo)); + if ((*fi)->cmi == NULL) {error = storageProblem; return(error);} + code = parseCharMetrics(fp, *fi); + } + else + { + if (flags & P_W) + { + (*fi)->cwi = (int *) calloc(256, sizeof(int)); + if ((*fi)->cwi == NULL) + { + error = storageProblem; + return(error); + } + } + /* parse section regardless */ + code = parseCharWidths(fp, (*fi)->cwi); + } /* else */ + } /* if */ + + if ((error != earlyEOF) && (code < 0)) error = code; + + /* The remaining sections of the AFM are optional. This code will */ + /* look at the next keyword in the file to determine what section */ + /* is next, and then allocate the appropriate amount of storage */ + /* for the data (if the data is to be saved) and call the */ + /* appropriate parsing routine to parse the section. */ + + while ((code != normalEOF) && (code != earlyEOF)) + { + keyword = token(fp); + if (keyword == NULL) + /* Have reached an early and unexpected EOF. */ + /* Set flag and stop parsing */ + { + code = earlyEOF; + break; /* get out of loop */ + } + switch(recognize(keyword)) + { + case STARTKERNDATA: + break; + case ENDKERNDATA: + break; + case STARTTRACKKERN: + keyword = token(fp); + if (flags & P_T) + { + (*fi)->numOfTracks = atoi(keyword); + (*fi)->tkd = (TrackKernData *) + calloc((*fi)->numOfTracks, sizeof(TrackKernData)); + if ((*fi)->tkd == NULL) + { + error = storageProblem; + return(error); + } + } /* if */ + code = parseTrackKernData(fp, *fi); + break; + case STARTKERNPAIRS: + keyword = token(fp); + if (flags & P_P) + { + (*fi)->numOfPairs = atoi(keyword); + (*fi)->pkd = (PairKernData *) + calloc((*fi)->numOfPairs, sizeof(PairKernData)); + if ((*fi)->pkd == NULL) + { + error = storageProblem; + return(error); + } + } /* if */ + code = parsePairKernData(fp, *fi); + break; + case STARTCOMPOSITES: + keyword = token(fp); + if (flags & P_C) + { + (*fi)->numOfComps = atoi(keyword); + (*fi)->ccd = (CompCharData *) + calloc((*fi)->numOfComps, sizeof(CompCharData)); + if ((*fi)->ccd == NULL) + { + error = storageProblem; + return(error); + } + } /* if */ + code = parseCompCharData(fp, *fi); + break; + case ENDFONTMETRICS: + code = normalEOF; + break; + case NOPE: + default: + code = parseError; + break; + } /* switch */ + + if ((error != earlyEOF) && (code < 0)) error = code; + + } /* while */ + + if ((error != earlyEOF) && (code < 0)) error = code; + + if (ident != NULL) { free(ident); ident = NULL; } + + return(error); + +} /* parseFile */ + +/* added for MDVI: this function was copied from `parseAFMclient.c' */ + +void afm_free_fontinfo(FontInfo *fi) +{ + if (fi != NULL) + { + if (fi->gfi != NULL) + { + free(fi->gfi->afmVersion); fi->gfi->afmVersion = NULL; + free(fi->gfi->fontName); fi->gfi->fontName = NULL; + free(fi->gfi->fullName); fi->gfi->fullName = NULL; + free(fi->gfi->familyName); fi->gfi->familyName = NULL; + free(fi->gfi->weight); fi->gfi->weight = NULL; + free(fi->gfi->version); fi->gfi->version = NULL; + free(fi->gfi->notice); fi->gfi->notice = NULL; + free(fi->gfi->encodingScheme); fi->gfi->encodingScheme = NULL; + free(fi->gfi); fi->gfi = NULL; + } + + if (fi->cwi != NULL) + { free(fi->cwi); fi->cwi = NULL; } + + if (fi->cmi != NULL) + { + int i = 0; + CharMetricInfo *temp = fi->cmi; + Ligature *node = temp->ligs; + + for (i = 0; i < fi->numOfChars; ++i) + { + for (node = temp->ligs; node != NULL; node = node->next) + { + free(node->succ); node->succ = NULL; + free(node->lig); node->lig = NULL; + } + + free(temp->name); temp->name = NULL; + temp++; + } + + free(fi->cmi); fi->cmi = NULL; + } + + if (fi->tkd != NULL) + { free(fi->tkd); fi->tkd = NULL; } + + if (fi->pkd != NULL) + { + free(fi->pkd->name1); fi->pkd->name1 = NULL; + free(fi->pkd->name2); fi->pkd->name2 = NULL; + free(fi->pkd); fi->pkd = NULL; + } + + if (fi->ccd != NULL) + { + int i = 0, j = 0; + CompCharData *ccd = fi->ccd; + + for (i = 0; i < fi->numOfComps; ++i) + { + for (j = 0; j < ccd[i].numOfPieces; ++j) + { + free(ccd[i].pieces[j].pccName); + ccd[i].pieces[j].pccName = NULL; + } + + free(ccd[i].ccName); ccd[i].ccName = NULL; + } + + free(fi->ccd); fi->ccd = NULL; + } + + free(fi); + + } /* if */ + +} /* afm_free_fontinfo */ + +#endif /* WITH_AFM_FILES */ diff --git a/backend/dvi/mdvi-lib/afmparse.h b/backend/dvi/mdvi-lib/afmparse.h new file mode 100644 index 00000000..6cce780c --- /dev/null +++ b/backend/dvi/mdvi-lib/afmparse.h @@ -0,0 +1,307 @@ +/* modified for MDVI -- some names changed to avoid conflicts with T1lib */ +/* + * (C) 1988, 1989 by Adobe Systems Incorporated. All rights reserved. + * + * This file may be freely copied and redistributed as long as: + * 1) This entire notice continues to be included in the file, + * 2) If the file has been modified in any way, a notice of such + * modification is conspicuously indicated. + * + * PostScript, Display PostScript, and Adobe are registered trademarks of + * Adobe Systems Incorporated. + * + * ************************************************************************ + * THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO CHANGE WITHOUT + * NOTICE, AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY ADOBE SYSTEMS + * INCORPORATED. ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY OR + * LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO WARRANTY OF ANY + * KIND (EXPRESS, IMPLIED OR STATUTORY) WITH RESPECT TO THIS INFORMATION, + * AND EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * ************************************************************************ + */ + +/* ParseAFM.h + * + * This header file is used in conjuction with the parseAFM.c file. + * Together these files provide the functionality to parse Adobe Font + * Metrics files and store the information in predefined data structures. + * It is intended to work with an application program that needs font metric + * information. The program can be used as is by making a procedure call to + * parse an AFM file and have the data stored, or an application developer + * may wish to customize the code. + * + * This header file defines the data structures used as well as the key + * strings that are currently recognized by this version of the AFM parser. + * This program is based on the document "Adobe Font Metrics Files, + * Specification Version 2.0". + * + * AFM files are separated into distinct sections of different data. Because + * of this, the parseAFM program can parse a specified file to only save + * certain sections of information based on the application's needs. A record + * containing the requested information will be returned to the application. + * + * AFM files are divided into five sections of data: + * 1) The Global Font Information + * 2) The Character Metrics Information + * 3) The Track Kerning Data + * 4) The Pair-Wise Kerning Data + * 5) The Composite Character Data + * + * Basically, the application can request any of these sections independent + * of what other sections are requested. In addition, in recognizing that + * many applications will want ONLY the x-width of characters and not all + * of the other character metrics information, there is a way to receive + * only the width information so as not to pay the storage cost for the + * unwanted data. An application should never request both the + * "quick and dirty" char metrics (widths only) and the Character Metrics + * Information since the Character Metrics Information will contain all + * of the character widths as well. + * + * There is a procedure in parseAFM.c, called parseFile, that can be + * called from any application wishing to get information from the AFM File. + * This procedure expects 3 parameters: a vaild file descriptor, a pointer + * to a (FontInfo *) variable (for which space will be allocated and then + * will be filled in with the data requested), and a mask specifying + * which data from the AFM File should be saved in the FontInfo structure. + * + * The flags that can be used to set the appropriate mask are defined below. + * In addition, several commonly used masks have already been defined. + * + * History: + * original: DSM Thu Oct 20 17:39:59 PDT 1988 + * modified: DSM Mon Jul 3 14:17:50 PDT 1989 + * - added 'storageProblem' return code + * - fixed typos + */ +#ifndef _MDVI_PARSEAFM_H +#define _MDVI_PARSEAFM_H 1 + +#include "sysdeps.h" + +#include + +/* your basic constants */ +#define TRUE 1 +#define FALSE 0 +#define EOL '\n' /* end-of-line indicator */ +#define MAX_NAME 4096 /* max length for identifiers */ +#define BOOL int +#define FLAGS int + +/* Flags that can be AND'ed together to specify exactly what + * information from the AFM file should be saved. */ +#define P_G 0x01 /* 0000 0001 */ /* Global Font Info */ +#define P_W 0x02 /* 0000 0010 */ /* Character Widths ONLY */ +#define P_M 0x06 /* 0000 0110 */ /* All Char Metric Info */ +#define P_P 0x08 /* 0000 1000 */ /* Pair Kerning Info */ +#define P_T 0x10 /* 0001 0000 */ /* Track Kerning Info */ +#define P_C 0x20 /* 0010 0000 */ /* Composite Char Info */ + +/* Commonly used flags */ +#define P_GW (P_G | P_W) +#define P_GM (P_G | P_M) +#define P_GMP (P_G | P_M | P_P) +#define P_GMK (P_G | P_M | P_P | P_T) +#define P_ALL (P_G | P_M | P_P | P_T | P_C) + +/* Possible return codes from the parseFile procedure. + * + * ok means there were no problems parsing the file. + * + * parseError means that there was some kind of parsing error, but the + * parser went on. This could include problems like the count for any given + * section does not add up to how many entries there actually were, or + * there was a key that was not recognized. The return record may contain + * vaild data or it may not. + * + * earlyEOF means that an End of File was encountered before expected. This + * may mean that the AFM file had been truncated, or improperly formed. + * + * storageProblem means that there were problems allocating storage for + * the data structures that would have contained the AFM data. + */ +#define ok 0 +#define parseError -1 +#define earlyEOF -2 +#define storageProblem -3 + +/************************* TYPES *********************************/ +/* Below are all of the data structure definitions. These structures + * try to map as closely as possible to grouping and naming of data + * in the AFM Files. + */ + +/* Bounding box definition. Used for the Font BBox as well as the + * Character BBox. + */ +typedef struct +{ + int llx; /* lower left x-position */ + int lly; /* lower left y-position */ + int urx; /* upper right x-position */ + int ury; /* upper right y-position */ +} BBox; + +/* Global Font information. + * The key that each field is associated with is in comments. For an + * explanation about each key and its value please refer to the AFM + * documentation (full title & version given above). + */ +typedef struct +{ + char *afmVersion; /* key: StartFontMetrics */ + char *fontName; /* key: FontName */ + char *fullName; /* key: FullName */ + char *familyName; /* key: FamilyName */ + char *weight; /* key: Weight */ + float italicAngle; /* key: ItalicAngle */ + BOOL isFixedPitch; /* key: IsFixedPitch */ + BBox fontBBox; /* key: FontBBox */ + int underlinePosition; /* key: UnderlinePosition */ + int underlineThickness; /* key: UnderlineThickness */ + char *version; /* key: Version */ + char *notice; /* key: Notice */ + char *encodingScheme; /* key: EncodingScheme */ + int capHeight; /* key: CapHeight */ + int xHeight; /* key: XHeight */ + int ascender; /* key: Ascender */ + int descender; /* key: Descender */ +} GlobalFontInfo; + +/* Ligature definition is a linked list since any character can have + * any number of ligatures. + */ +typedef struct _t_ligature +{ + char *succ, *lig; + struct _t_ligature *next; +} Ligature; + +/* Character Metric Information. This structure is used only if ALL + * character metric information is requested. If only the character + * widths is requested, then only an array of the character x-widths + * is returned. + * + * The key that each field is associated with is in comments. For an + * explanation about each key and its value please refer to the + * Character Metrics section of the AFM documentation (full title + * & version given above). + */ +typedef struct +{ + int code, /* key: C */ + wx, /* key: WX */ + wy; /* together wx and wy are associated with key: W */ + char *name; /* key: N */ + BBox charBBox; /* key: B */ + Ligature *ligs; /* key: L (linked list; not a fixed number of Ls */ +} CharMetricInfo; + +/* Track kerning data structure. + * The fields of this record are the five values associated with every + * TrackKern entry. + * + * For an explanation about each value please refer to the + * Track Kerning section of the AFM documentation (full title + * & version given above). + */ +typedef struct +{ + int degree; + float minPtSize, + minKernAmt, + maxPtSize, + maxKernAmt; +} TrackKernData; + +/* Pair Kerning data structure. + * The fields of this record are the four values associated with every + * KP entry. For KPX entries, the yamt will be zero. + * + * For an explanation about each value please refer to the + * Pair Kerning section of the AFM documentation (full title + * & version given above). + */ +typedef struct +{ + char *name1; + char *name2; + int xamt, + yamt; +} PairKernData; + +/* PCC is a piece of a composite character. This is a sub structure of a + * compCharData described below. + * These fields will be filled in with the values from the key PCC. + * + * For an explanation about each key and its value please refer to the + * Composite Character section of the AFM documentation (full title + * & version given above). + */ +typedef struct +{ + char *pccName; + int deltax, + deltay; +} Pcc; + +/* Composite Character Information data structure. + * The fields ccName and numOfPieces are filled with the values associated + * with the key CC. The field pieces points to an array (size = numOfPieces) + * of information about each of the parts of the composite character. That + * array is filled in with the values from the key PCC. + * + * For an explanation about each key and its value please refer to the + * Composite Character section of the AFM documentation (full title + * & version given above). + */ +typedef struct +{ + char *ccName; + int numOfPieces; + Pcc *pieces; +} CompCharData; + +/* FontInfo + * Record type containing pointers to all of the other data + * structures containing information about a font. + * A a record of this type is filled with data by the + * parseFile function. + */ +typedef struct +{ + GlobalFontInfo *gfi; /* ptr to a GlobalFontInfo record */ + int *cwi; /* ptr to 256 element array of just char widths */ + int numOfChars; /* number of entries in char metrics array */ + CharMetricInfo *cmi; /* ptr to char metrics array */ + int numOfTracks; /* number to entries in track kerning array */ + TrackKernData *tkd; /* ptr to track kerning array */ + int numOfPairs; /* number to entries in pair kerning array */ + PairKernData *pkd; /* ptr to pair kerning array */ + int numOfComps; /* number to entries in comp char array */ + CompCharData *ccd; /* ptr to comp char array */ +} FontInfo; + +/************************* PROCEDURES ****************************/ + +/* Call this procedure to do the grunt work of parsing an AFM file. + * + * "fp" should be a valid file pointer to an AFM file. + * + * "fi" is a pointer to a pointer to a FontInfo record sturcture + * (defined above). Storage for the FontInfo structure will be + * allocated in parseFile and the structure will be filled in + * with the requested data from the AFM File. + * + * "flags" is a mask with bits set representing what data should + * be saved. Defined above are valid flags that can be used to set + * the mask, as well as a few commonly used masks. + * + * The possible return codes from parseFile are defined above. + */ + +extern int afm_parse_file __PROTO((FILE *, FontInfo **, FLAGS)); +extern void afm_free_fontinfo __PROTO((FontInfo *)); + +#endif /* _MDVI_PARSEAFM_H */ diff --git a/backend/dvi/mdvi-lib/bitmap.c b/backend/dvi/mdvi-lib/bitmap.c new file mode 100644 index 00000000..53f21207 --- /dev/null +++ b/backend/dvi/mdvi-lib/bitmap.c @@ -0,0 +1,1035 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* Bitmap manipulation routines */ + +#include +#include + +#include "mdvi.h" +#include "color.h" + +/* bit_masks[n] contains a BmUnit with `n' contiguous bits */ + +static BmUnit bit_masks[] = { + 0x0, 0x1, 0x3, 0x7, + 0xf, 0x1f, 0x3f, 0x7f, + 0xff, +#if BITMAP_BYTES > 1 + 0x1ff, 0x3ff, 0x7ff, + 0xfff, 0x1fff, 0x3fff, 0x7fff, + 0xffff, +#if BITMAP_BYTES > 2 + 0x1ffff, 0x3ffff, 0x7ffff, + 0xfffff, 0x1fffff, 0x3fffff, 0x7fffff, + 0xffffff, 0x1ffffff, 0x3ffffff, 0x7ffffff, + 0xfffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, + 0xffffffff +#endif /* BITMAP_BYTES > 2 */ +#endif /* BITMAP_BYTES > 1 */ +}; + +#ifndef NODEBUG +#define SHOW_OP_DATA (DEBUGGING(BITMAP_OPS) && DEBUGGING(BITMAP_DATA)) +#endif + +/* + * Some useful macros to manipulate bitmap data + * SEGMENT(m,n) = bit mask for a segment of `m' contiguous bits + * starting at column `n'. These macros assume that + * m + n <= BITMAP_BITS, 0 <= m, n. + */ +#ifdef WORD_BIG_ENDIAN +#define SEGMENT(m,n) (bit_masks[m] << (BITMAP_BITS - (m) - (n))) +#else +#define SEGMENT(m,n) (bit_masks[m] << (n)) +#endif + +/* sampling and shrinking routines shamelessly stolen from xdvi */ + +static int sample_count[] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 +}; + +/* bit_swap[j] = j with all bits inverted (i.e. msb -> lsb) */ +static Uchar bit_swap[] = { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, + 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, + 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, + 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, + 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, + 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, + 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, + 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, + 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, + 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff +}; + + +/* + * next we have three bitmap functions to convert bitmaps in LSB bit order + * with 8, 16 and 32 bits per unit, to our internal format. The differences + * are minimal, but writing a generic function to handle all unit sizes is + * hopelessly slow. + */ + +BITMAP *bitmap_convert_lsb8(Uchar *bits, int w, int h, int stride) +{ + BITMAP *bm; + int i; + Uchar *unit; + register Uchar *curr; + Uchar *end; + int bytes; + + DEBUG((DBG_BITMAP_OPS, "convert LSB %dx%d@8 -> bitmap\n", w, h)); + + bm = bitmap_alloc_raw(w, h); + + /* this is the number of bytes in the original bitmap */ + bytes = ROUND(w, 8); + unit = (Uchar *)bm->data; + end = unit + bm->stride; + curr = bits; + /* we try to do this as fast as we can */ + for(i = 0; i < h; i++) { +#ifdef WORD_LITTLE_ENDIAN + memcpy(unit, curr, bytes); + curr += stride; +#else + int j; + + for(j = 0; j < bytes; curr++, j++) + unit[j] = bit_swap[*curr]; + cur += stride - bytes; +#endif + memzero(unit + bytes, bm->stride - bytes); + unit += bm->stride; + } + if(SHOW_OP_DATA) + bitmap_print(stderr, bm); + return bm; +} + +BITMAP *bitmap_convert_msb8(Uchar *data, int w, int h, int stride) +{ + BITMAP *bm; + Uchar *unit; + Uchar *curr; + int i; + int bytes; + + bm = bitmap_alloc(w, h); + bytes = ROUND(w, 8); + unit = (Uchar *)bm->data; + curr = data; + for(i = 0; i < h; i++) { +#ifdef WORD_LITTLE_ENDIAN + int j; + + for(j = 0; j < bytes; curr++, j++) + unit[j] = bit_swap[*curr]; + curr += stride - bytes; +#else + memcpy(unit, curr, bytes); + curr += stride; +#endif + memzero(unit + bytes, bm->stride - bytes); + unit += bm->stride; + } + if(SHOW_OP_DATA) + bitmap_print(stderr, bm); + return bm; +} + + +BITMAP *bitmap_copy(BITMAP *bm) +{ + BITMAP *nb = bitmap_alloc(bm->width, bm->height); + + DEBUG((DBG_BITMAP_OPS, "copy %dx%d\n", bm->width, bm->height)); + memcpy(nb->data, bm->data, bm->height * bm->stride); + return nb; +} + +BITMAP *bitmap_alloc(int w, int h) +{ + BITMAP *bm; + + bm = xalloc(BITMAP); + bm->width = w; + bm->height = h; + bm->stride = BM_BYTES_PER_LINE(bm); + if(h && bm->stride) + bm->data = (BmUnit *)mdvi_calloc(h, bm->stride); + else + bm->data = NULL; + + return bm; +} + +BITMAP *bitmap_alloc_raw(int w, int h) +{ + BITMAP *bm; + + bm = xalloc(BITMAP); + bm->width = w; + bm->height = h; + bm->stride = BM_BYTES_PER_LINE(bm); + if(h && bm->stride) + bm->data = (BmUnit *)mdvi_malloc(h * bm->stride); + else + bm->data = NULL; + + return bm; +} + +void bitmap_destroy(BITMAP *bm) +{ + if(bm->data) + free(bm->data); + free(bm); +} + +void bitmap_print(FILE *out, BITMAP *bm) +{ + int i, j; + BmUnit *a, mask; + static const char labels[] = { + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' + }; + int sub; + + a = bm->data; + fprintf(out, " "); + if(bm->width > 10) { + putchar('0'); + sub = 0; + for(j = 2; j <= bm->width; j++) + if((j %10) == 0) { + if((j % 100) == 0) { + fprintf(out, "*"); + sub += 100; + } else + fprintf(out, "%d", (j - sub)/10); + } else + putc(' ', out); + fprintf(out, "\n "); + } + for(j = 0; j < bm->width; j++) + putc(labels[j % 10], out); + putchar('\n'); + for(i = 0; i < bm->height; i++) { + mask = FIRSTMASK; + a = (BmUnit *)((char *)bm->data + i * bm->stride); + fprintf(out, "%3d ", i+1); + for(j = 0; j < bm->width; j++) { + if(*a & mask) + putc('#', out); + else + putc('.', out); + if(mask == LASTMASK) { + a++; + mask = FIRSTMASK; + } else + NEXTMASK(mask); + } + putchar('\n'); + } +} + +void bitmap_set_col(BITMAP *bm, int row, int col, int count, int state) +{ + BmUnit *ptr; + BmUnit mask; + + ptr = __bm_unit_ptr(bm, col, row); + mask = FIRSTMASKAT(col); + + while(count-- > 0) { + if(state) + *ptr |= mask; + else + *ptr &= ~mask; + /* move to next row */ + ptr = bm_offset(ptr, bm->stride); + } +} + +/* + * to use this function you should first make sure that + * there is room for `count' bits in the scanline + * + * A general-purpose (but not very efficient) function to paint `n' pixels + * on a bitmap, starting at position (x, y) would be: + * + * bitmap_paint_bits(__bm_unit_ptr(bitmap, x, y), x % BITMAP_BITS, n) + * + */ +void bitmap_paint_bits(BmUnit *ptr, int n, int count) +{ + /* paint the head */ + if(n + count > BITMAP_BITS) { + *ptr |= SEGMENT(BITMAP_BITS - n, n); + count -= BITMAP_BITS - n; + ptr++; + } else { + *ptr |= SEGMENT(count, n); + return; + } + + /* paint the middle */ + for(; count >= BITMAP_BITS; count -= BITMAP_BITS) + *ptr++ = bit_masks[BITMAP_BITS]; + + /* paint the tail */ + if(count > 0) + *ptr |= SEGMENT(count, 0); +} + +/* + * same as paint_bits but clears pixels instead of painting them. Written + * as a separate function for efficiency reasons. + */ +void bitmap_clear_bits(BmUnit *ptr, int n, int count) +{ + if(n + count > BITMAP_BITS) { + *ptr &= ~SEGMENT(BITMAP_BITS - n, n); + count -= BITMAP_BITS; + ptr++; + } else { + *ptr &= ~SEGMENT(count, n); + return; + } + + for(; count >= BITMAP_BITS; count -= BITMAP_BITS) + *ptr++ = 0; + + if(count > 0) + *ptr &= ~SEGMENT(count, 0); +} + +/* the general function to paint rows. Still used by the PK reader, but that + * will change soon (The GF reader already uses bitmap_paint_bits()). + */ +void bitmap_set_row(BITMAP *bm, int row, int col, int count, int state) +{ + BmUnit *ptr; + + ptr = __bm_unit_ptr(bm, col, row); + if(state) + bitmap_paint_bits(ptr, col & (BITMAP_BITS-1), count); + else + bitmap_clear_bits(ptr, col & (BITMAP_BITS-1), count); +} + +/* + * Now several `flipping' operations + */ + +void bitmap_flip_horizontally(BITMAP *bm) +{ + BITMAP nb; + BmUnit *fptr, *tptr; + BmUnit fmask, tmask; + int w, h; + + nb.width = bm->width; + nb.height = bm->height; + nb.stride = bm->stride; + nb.data = mdvi_calloc(bm->height, bm->stride); + + fptr = bm->data; + tptr = __bm_unit_ptr(&nb, nb.width-1, 0); + for(h = 0; h < bm->height; h++) { + BmUnit *fline, *tline; + + fline = fptr; + tline = tptr; + fmask = FIRSTMASK; + tmask = FIRSTMASKAT(nb.width-1); + for(w = 0; w < bm->width; w++) { + if(*fline & fmask) + *tline |= tmask; + if(fmask == LASTMASK) { + fmask = FIRSTMASK; + fline++; + } else + NEXTMASK(fmask); + if(tmask == FIRSTMASK) { + tmask = LASTMASK; + tline--; + } else + PREVMASK(tmask); + } + fptr = bm_offset(fptr, bm->stride); + tptr = bm_offset(tptr, bm->stride); + } + DEBUG((DBG_BITMAP_OPS, "flip_horizontally (%d,%d) -> (%d,%d)\n", + bm->width, bm->height, nb.width, nb.height)); + mdvi_free(bm->data); + bm->data = nb.data; + if(SHOW_OP_DATA) + bitmap_print(stderr, bm); +} + +void bitmap_flip_vertically(BITMAP *bm) +{ + BITMAP nb; + BmUnit *fptr, *tptr; + BmUnit fmask; + int w, h; + + nb.width = bm->width; + nb.height = bm->height; + nb.stride = bm->stride; + nb.data = mdvi_calloc(bm->height, bm->stride); + + fptr = bm->data; + tptr = __bm_unit_ptr(&nb, 0, nb.height-1); + for(h = 0; h < bm->height; h++) { + BmUnit *fline, *tline; + + fline = fptr; + tline = tptr; + fmask = FIRSTMASK; + for(w = 0; w < bm->width; w++) { + if(*fline & fmask) + *tline |= fmask; + if(fmask == LASTMASK) { + fmask = FIRSTMASK; + fline++; + tline++; + } else + NEXTMASK(fmask); + } + fptr = bm_offset(fptr, bm->stride); + tptr = (BmUnit *)((char *)tptr - bm->stride); + } + DEBUG((DBG_BITMAP_OPS, "flip_vertically (%d,%d) -> (%d,%d)\n", + bm->width, bm->height, nb.width, nb.height)); + mdvi_free(bm->data); + bm->data = nb.data; + if(SHOW_OP_DATA) + bitmap_print(stderr, bm); +} + +void bitmap_flip_diagonally(BITMAP *bm) +{ + BITMAP nb; + BmUnit *fptr, *tptr; + BmUnit fmask, tmask; + int w, h; + + nb.width = bm->width; + nb.height = bm->height; + nb.stride = bm->stride; + nb.data = mdvi_calloc(bm->height, bm->stride); + + fptr = bm->data; + tptr = __bm_unit_ptr(&nb, nb.width-1, nb.height-1); + for(h = 0; h < bm->height; h++) { + BmUnit *fline, *tline; + + fline = fptr; + tline = tptr; + fmask = FIRSTMASK; + tmask = FIRSTMASKAT(nb.width-1); + for(w = 0; w < bm->width; w++) { + if(*fline & fmask) + *tline |= tmask; + if(fmask == LASTMASK) { + fmask = FIRSTMASK; + fline++; + } else + NEXTMASK(fmask); + if(tmask == FIRSTMASK) { + tmask = LASTMASK; + tline--; + } else + PREVMASK(tmask); + } + fptr = bm_offset(fptr, bm->stride); + tptr = bm_offset(tptr, -nb.stride); + } + DEBUG((DBG_BITMAP_OPS, "flip_diagonally (%d,%d) -> (%d,%d)\n", + bm->width, bm->height, nb.width, nb.height)); + mdvi_free(bm->data); + bm->data = nb.data; + if(SHOW_OP_DATA) + bitmap_print(stderr, bm); +} + +void bitmap_rotate_clockwise(BITMAP *bm) +{ + BITMAP nb; + BmUnit *fptr, *tptr; + BmUnit fmask, tmask; + int w, h; + + nb.width = bm->height; + nb.height = bm->width; + nb.stride = BM_BYTES_PER_LINE(&nb); + nb.data = mdvi_calloc(nb.height, nb.stride); + + fptr = bm->data; + tptr = __bm_unit_ptr(&nb, nb.width - 1, 0); + + tmask = FIRSTMASKAT(nb.width-1); + for(h = 0; h < bm->height; h++) { + BmUnit *fline, *tline; + + fmask = FIRSTMASK; + fline = fptr; + tline = tptr; + for(w = 0; w < bm->width; w++) { + if(*fline & fmask) + *tline |= tmask; + if(fmask == LASTMASK) { + fmask = FIRSTMASK; + fline++; + } else + NEXTMASK(fmask); + /* go to next row */ + tline = bm_offset(tline, nb.stride); + } + fptr = bm_offset(fptr, bm->stride); + if(tmask == FIRSTMASK) { + tmask = LASTMASK; + tptr--; + } else + PREVMASK(tmask); + } + + DEBUG((DBG_BITMAP_OPS, "rotate_clockwise (%d,%d) -> (%d,%d)\n", + bm->width, bm->height, nb.width, nb.height)); + mdvi_free(bm->data); + bm->data = nb.data; + bm->width = nb.width; + bm->height = nb.height; + bm->stride = nb.stride; + if(SHOW_OP_DATA) + bitmap_print(stderr, bm); +} + +void bitmap_rotate_counter_clockwise(BITMAP *bm) +{ + BITMAP nb; + BmUnit *fptr, *tptr; + BmUnit fmask, tmask; + int w, h; + + nb.width = bm->height; + nb.height = bm->width; + nb.stride = BM_BYTES_PER_LINE(&nb); + nb.data = mdvi_calloc(nb.height, nb.stride); + + fptr = bm->data; + tptr = __bm_unit_ptr(&nb, 0, nb.height - 1); + + tmask = FIRSTMASK; + for(h = 0; h < bm->height; h++) { + BmUnit *fline, *tline; + + fmask = FIRSTMASK; + fline = fptr; + tline = tptr; + for(w = 0; w < bm->width; w++) { + if(*fline & fmask) + *tline |= tmask; + if(fmask == LASTMASK) { + fmask = FIRSTMASK; + fline++; + } else + NEXTMASK(fmask); + /* go to previous row */ + tline = bm_offset(tline, -nb.stride); + } + fptr = bm_offset(fptr, bm->stride); + if(tmask == LASTMASK) { + tmask = FIRSTMASK; + tptr++; + } else + NEXTMASK(tmask); + } + + DEBUG((DBG_BITMAP_OPS, "rotate_counter_clockwise (%d,%d) -> (%d,%d)\n", + bm->width, bm->height, nb.width, nb.height)); + mdvi_free(bm->data); + bm->data = nb.data; + bm->width = nb.width; + bm->height = nb.height; + bm->stride = nb.stride; + if(SHOW_OP_DATA) + bitmap_print(stderr, bm); +} + +void bitmap_flip_rotate_clockwise(BITMAP *bm) +{ + BITMAP nb; + BmUnit *fptr, *tptr; + BmUnit fmask, tmask; + int w, h; + + nb.width = bm->height; + nb.height = bm->width; + nb.stride = BM_BYTES_PER_LINE(&nb); + nb.data = mdvi_calloc(nb.height, nb.stride); + + fptr = bm->data; + tptr = __bm_unit_ptr(&nb, nb.width-1, nb.height-1); + + tmask = FIRSTMASKAT(nb.width-1); + for(h = 0; h < bm->height; h++) { + BmUnit *fline, *tline; + + fmask = FIRSTMASK; + fline = fptr; + tline = tptr; + for(w = 0; w < bm->width; w++) { + if(*fline & fmask) + *tline |= tmask; + if(fmask == LASTMASK) { + fmask = FIRSTMASK; + fline++; + } else + NEXTMASK(fmask); + /* go to previous line */ + tline = bm_offset(tline, -nb.stride); + } + fptr = bm_offset(fptr, bm->stride); + if(tmask == FIRSTMASK) { + tmask = LASTMASK; + tptr--; + } else + PREVMASK(tmask); + } + DEBUG((DBG_BITMAP_OPS, "flip_rotate_clockwise (%d,%d) -> (%d,%d)\n", + bm->width, bm->height, nb.width, nb.height)); + mdvi_free(bm->data); + bm->data = nb.data; + bm->width = nb.width; + bm->height = nb.height; + bm->stride = nb.stride; + if(SHOW_OP_DATA) + bitmap_print(stderr, bm); +} + +void bitmap_flip_rotate_counter_clockwise(BITMAP *bm) +{ + BITMAP nb; + BmUnit *fptr, *tptr; + BmUnit fmask, tmask; + int w, h; + + nb.width = bm->height; + nb.height = bm->width; + nb.stride = BM_BYTES_PER_LINE(&nb); + nb.data = mdvi_calloc(nb.height, nb.stride); + + fptr = bm->data; + tptr = nb.data; + tmask = FIRSTMASK; + + for(h = 0; h < bm->height; h++) { + BmUnit *fline, *tline; + + fmask = FIRSTMASK; + fline = fptr; + tline = tptr; + for(w = 0; w < bm->width; w++) { + if(*fline & fmask) + *tline |= tmask; + if(fmask == LASTMASK) { + fmask = FIRSTMASK; + fline++; + } else + NEXTMASK(fmask); + /* go to next line */ + tline = bm_offset(tline, nb.stride); + } + fptr = bm_offset(fptr, bm->stride); + if(tmask == LASTMASK) { + tmask = FIRSTMASK; + tptr++; + } else + NEXTMASK(tmask); + } + + DEBUG((DBG_BITMAP_OPS, "flip_rotate_counter_clockwise (%d,%d) -> (%d,%d)\n", + bm->width, bm->height, nb.width, nb.height)); + mdvi_free(bm->data); + bm->data = nb.data; + bm->width = nb.width; + bm->height = nb.height; + bm->stride = nb.stride; + if(SHOW_OP_DATA) + bitmap_print(stderr, bm); +} + +#if 0 +void bitmap_transform(BITMAP *map, DviOrientation orient) +{ + switch(orient) { + case MDVI_ORIENT_TBLR: + break; + case MDVI_ORIENT_TBRL: + bitmap_flip_horizontally(map); + break; + case MDVI_ORIENT_BTLR: + bitmap_flip_vertically(map); + break; + case MDVI_ORIENT_BTRL: + bitmap_flip_diagonally(map); + break; + case MDVI_ORIENT_RP90: + bitmap_rotate_counter_clockwise(map); + break; + case MDVI_ORIENT_RM90: + bitmap_rotate_clockwise(map); + break; + case MDVI_ORIENT_IRP90: + bitmap_flip_rotate_counter_clockwise(map); + break; + case MDVI_ORIENT_IRM90: + bitmap_flip_rotate_clockwise(map); + break; + } +} +#endif + +/* + * Count the number of non-zero bits in a box of dimensions w x h, starting + * at column `step' in row `data'. + * + * Shamelessly stolen from xdvi. + */ +static int do_sample(BmUnit *data, int stride, int step, int w, int h) +{ + BmUnit *ptr, *end, *cp; + int shift, n; + int bits_left; + int wid; + + ptr = data + step / BITMAP_BITS; + end = bm_offset(data, h * stride); + shift = FIRSTSHIFTAT(step); + bits_left = w; + n = 0; + while(bits_left) { +#ifndef WORD_BIG_ENDIAN + wid = BITMAP_BITS - shift; +#else + wid = shift; +#endif + if(wid > bits_left) + wid = bits_left; + if(wid > 8) + wid = 8; +#ifdef WORD_BIG_ENDIAN + shift -= wid; +#endif + for(cp = ptr; cp < end; cp = bm_offset(cp, stride)) + n += sample_count[(*cp >> shift) & bit_masks[wid]]; +#ifndef WORD_BIG_ENDIAN + shift += wid; +#endif +#ifdef WORD_BIG_ENDIAN + if(shift == 0) { + shift = BITMAP_BITS; + ptr++; + } +#else + if(shift == BITMAP_BITS) { + shift = 0; + ptr++; + } +#endif + bits_left -= wid; + } + return n; +} + +void mdvi_shrink_box(DviContext *dvi, DviFont *font, + DviFontChar *pk, DviGlyph *dest) +{ + int x, y, z; + DviGlyph *glyph; + int hs, vs; + + hs = dvi->params.hshrink; + vs = dvi->params.vshrink; + glyph = &pk->glyph; + + x = (int)glyph->x / hs; + if((int)glyph->x - x * hs > 0) + x++; + dest->w = x + ROUND((int)glyph->w - glyph->x, hs); + + z = (int)glyph->y + 1; + y = z / vs; + if(z - y * vs <= 0) + y--; + dest->h = y + ROUND((int)glyph->h - z, vs) + 1; + dest->x = x; + dest->y = glyph->y / vs; + dest->data = MDVI_GLYPH_EMPTY; + DEBUG((DBG_BITMAPS, "shrink_box: (%dw,%dh,%dx,%dy) -> (%dw,%dh,%dx,%dy)\n", + glyph->w, glyph->h, glyph->x, glyph->y, + dest->w, dest->h, dest->x, dest->y)); +} + +void mdvi_shrink_glyph(DviContext *dvi, DviFont *font, + DviFontChar *pk, DviGlyph *dest) +{ + int rows_left, rows, init_cols; + int cols_left, cols; + BmUnit *old_ptr, *new_ptr; + BITMAP *oldmap, *newmap; + BmUnit m, *cp; + DviGlyph *glyph; + int sample, min_sample; + int old_stride; + int new_stride; + int x, y; + int w, h; + int hs, vs; + + hs = dvi->params.hshrink; + vs = dvi->params.vshrink; + + min_sample = vs * hs * dvi->params.density / 100; + + glyph = &pk->glyph; + oldmap = (BITMAP *)glyph->data; + + x = (int)glyph->x / hs; + init_cols = (int)glyph->x - x * hs; + if(init_cols <= 0) + init_cols += hs; + else + x++; + w = x + ROUND((int)glyph->w - glyph->x, hs); + + cols = (int)glyph->y + 1; + y = cols / vs; + rows = cols - y * vs; + if(rows <= 0) { + rows += vs; + y--; + } + h = y + ROUND((int)glyph->h - cols, vs) + 1; + + /* create the new glyph */ + newmap = bitmap_alloc(w, h); + dest->data = newmap; + dest->x = x; + dest->y = glyph->y / vs; + dest->w = w; + dest->h = h; + + old_ptr = oldmap->data; + old_stride = oldmap->stride; + new_ptr = newmap->data; + new_stride = newmap->stride; + rows_left = glyph->h; + + while(rows_left) { + if(rows > rows_left) + rows = rows_left; + cols_left = glyph->w; + m = FIRSTMASK; + cp = new_ptr; + cols = init_cols; + while(cols_left > 0) { + if(cols > cols_left) + cols = cols_left; + sample = do_sample(old_ptr, old_stride, + glyph->w - cols_left, cols, rows); + if(sample >= min_sample) + *cp |= m; + if(m == LASTMASK) { + m = FIRSTMASK; + cp++; + } else + NEXTMASK(m); + cols_left -= cols; + cols = hs; + } + new_ptr = bm_offset(new_ptr, new_stride); + old_ptr = bm_offset(old_ptr, rows * old_stride); + rows_left -= rows; + rows = vs; + } + DEBUG((DBG_BITMAPS, "shrink_glyph: (%dw,%dh,%dx,%dy) -> (%dw,%dh,%dx,%dy)\n", + glyph->w, glyph->h, glyph->x, glyph->y, + dest->w, dest->h, dest->x, dest->y)); + if(DEBUGGING(BITMAP_DATA)) + bitmap_print(stderr, newmap); +} + +void mdvi_shrink_glyph_grey(DviContext *dvi, DviFont *font, + DviFontChar *pk, DviGlyph *dest) +{ + int rows_left, rows; + int cols_left, cols, init_cols; + long sampleval, samplemax; + BmUnit *old_ptr; + void *image; + int w, h; + int x, y; + DviGlyph *glyph; + BITMAP *map; + Ulong *pixels; + int npixels; + Ulong colortab[2]; + int hs, vs; + DviDevice *dev; + + hs = dvi->params.hshrink; + vs = dvi->params.vshrink; + dev = &dvi->device; + + glyph = &pk->glyph; + map = (BITMAP *)glyph->data; + + x = (int)glyph->x / hs; + init_cols = (int)glyph->x - x * hs; + if(init_cols <= 0) + init_cols += hs; + else + x++; + w = x + ROUND((int)glyph->w - glyph->x, hs); + + cols = (int)glyph->y + 1; + y = cols / vs; + rows = cols - y * vs; + if(rows <= 0) { + rows += vs; + y--; + } + h = y + ROUND((int)glyph->h - cols, vs) + 1; + ASSERT(w && h); + + /* before touching anything, do this */ + image = dev->create_image(dev->device_data, w, h, BITMAP_BITS); + if(image == NULL) { + mdvi_shrink_glyph(dvi, font, pk, dest); + return; + } + + /* save these colors */ + pk->fg = MDVI_CURRFG(dvi); + pk->bg = MDVI_CURRBG(dvi); + + samplemax = vs * hs; + npixels = samplemax + 1; + pixels = get_color_table(&dvi->device, npixels, pk->fg, pk->bg, + dvi->params.gamma, dvi->params.density); + if(pixels == NULL) { + npixels = 2; + colortab[0] = pk->fg; + colortab[1] = pk->bg; + pixels = &colortab[0]; + } + + /* setup the new glyph */ + dest->data = image; + dest->x = x; + dest->y = glyph->y / vs; + dest->w = w; + dest->h = h; + + y = 0; + old_ptr = map->data; + rows_left = glyph->h; + + while(rows_left && y < h) { + x = 0; + if(rows > rows_left) + rows = rows_left; + cols_left = glyph->w; + cols = init_cols; + while(cols_left && x < w) { + if(cols > cols_left) + cols = cols_left; + sampleval = do_sample(old_ptr, map->stride, + glyph->w - cols_left, cols, rows); + /* scale the sample value by the number of grey levels */ + if(npixels - 1 != samplemax) + sampleval = ((npixels-1) * sampleval) / samplemax; + ASSERT(sampleval < npixels); + dev->put_pixel(image, x, y, pixels[sampleval]); + cols_left -= cols; + cols = hs; + x++; + } + for(; x < w; x++) + dev->put_pixel(image, x, y, pixels[0]); + old_ptr = bm_offset(old_ptr, rows * map->stride); + rows_left -= rows; + rows = vs; + y++; + } + + for(; y < h; y++) { + for(x = 0; x < w; x++) + dev->put_pixel(image, x, y, pixels[0]); + } + DEBUG((DBG_BITMAPS, "shrink_glyph_grey: (%dw,%dh,%dx,%dy) -> (%dw,%dh,%dx,%dy)\n", + glyph->w, glyph->h, glyph->x, glyph->y, + dest->w, dest->h, dest->x, dest->y)); +} + diff --git a/backend/dvi/mdvi-lib/bitmap.h b/backend/dvi/mdvi-lib/bitmap.h new file mode 100644 index 00000000..4d98fecd --- /dev/null +++ b/backend/dvi/mdvi-lib/bitmap.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef _BITMAP_H +#define _BITMAP_H 1 + +#include "sysdeps.h" + +/* Structures and functions to manipulate bitmaps */ + +/* bitmap unit (as in X's docs) */ +typedef Uint32 BmUnit; + +/* size (in bytes) of a bitmap atom */ +#define BITMAP_BYTES 4 + +/* size (in bits) of a bitmap atom */ +#define BITMAP_BITS (BITMAP_BYTES << 3) + +typedef struct { + int width; + int height; + int stride; + BmUnit *data; +} BITMAP; + +#define BM_BYTES_PER_LINE(b) \ + (ROUND((b)->width, BITMAP_BITS) * BITMAP_BYTES) +#define BM_WIDTH(b) (((BITMAP *)(b))->width) +#define BM_HEIGHT(b) (((BITMAP *)(b))->height) + +#define BMBIT(n) ((BmUnit)1 << (n)) + +/* Macros to manipulate individual pixels in a bitmap + * (they are slow, don't use them) + */ + +#define bm_offset(b,o) (BmUnit *)((Uchar *)(b) + (o)) + +#define __bm_unit_ptr(b,x,y) \ + bm_offset((b)->data, (y) * (b)->stride + \ + ((x) / BITMAP_BITS) * BITMAP_BYTES) + +#define __bm_unit(b,x,y) __bm_unit_ptr((b), (x), (y))[0] + +#define BM_GETPIXEL(b,x,y) __bm_unit((b), (x), (y)) +#define BM_SETPIXEL(b,x,y) (__bm_unit((b), (x), (y)) |= FIRSTMASKAT(x)) +#define BM_CLRPIXEL(b,x,y) (__bm_unit((b), (x), (y)) &= ~FIRSTMASKAT(x)) + +/* + * These macros are used to access pixels in a bitmap. They are supposed + * to be used like this: + */ +#if 0 + BmUnit *row, mask; + + mask = FIRSTMASK; + + /* position `unit' at coordinates (column_number, row_number) */ + unit = (BmUnit *)((char *)bitmap->data + row_number * bitmap->stride + + (column_number / BITMAP_BITS); + /* loop over all pixels IN THE SAME ROW */ + for(i = 0; i < number_of_pixels; i++) { + /* to test if a pixel is set */ + if(*unit & mask) { + /* yes, it is, do something with it */ + } + /* to set/clear a pixel */ + if(painting) + *unit |= mask; /* now you see it */ + else + *unit &= ~mask; /* now you don't */ + /* move to next pixel */ + if(mask == LASTMASK) { + unit++; + UPDATEMASK(mask); + } + } +/* end of sample code */ +#endif + +/* bitmaps are stored in native byte order */ +#ifdef WORD_BIG_ENDIAN +#define FIRSTSHIFT (BITMAP_BITS - 1) +#define LASTSHIFT 0 +#define NEXTMASK(m) ((m) >>= 1) +#define PREVMASK(m) ((m) <<= 1) +#define FIRSTSHIFTAT(c) (BITMAP_BITS - ((c) % BITMAP_BITS) - 1) +#else +#define FIRSTSHIFT 0 +#define LASTSHIFT (BITMAP_BITS - 1) +#define NEXTMASK(m) ((m) <<= 1) +#define PREVMASK(m) ((m) >>= 1) +#define FIRSTSHIFTAT(c) ((c) % BITMAP_BITS) +#endif + +#define FIRSTMASK BMBIT(FIRSTSHIFT) +#define FIRSTMASKAT(c) BMBIT(FIRSTSHIFTAT(c)) +#define LASTMASK BMBIT(LASTSHIFT) + +extern BITMAP *bitmap_alloc __PROTO((int, int)); +extern BITMAP *bitmap_alloc_raw __PROTO((int, int)); +extern void bitmap_destroy __PROTO((BITMAP *)); + +/* + * set_row(bm, row, col, count, state): + * sets `count' pixels to state `onoff', starting from pixel + * at position (col, row). All pixels must lie in the same + * row. + */ +extern void bitmap_set_col __PROTO((BITMAP *, int, int, int, int)); +extern void bitmap_set_row __PROTO((BITMAP *, int, int, int, int)); + +extern void bitmap_paint_bits __PROTO((BmUnit *, int, int)); +extern void bitmap_clear_bits __PROTO((BmUnit *, int, int)); + +extern BITMAP *bitmap_copy __PROTO((BITMAP *)); +extern void bitmap_flip_horizontally __PROTO((BITMAP *)); +extern void bitmap_flip_vertically __PROTO((BITMAP *)); +extern void bitmap_flip_diagonally __PROTO((BITMAP *)); +extern void bitmap_rotate_clockwise __PROTO((BITMAP *)); +extern void bitmap_rotate_counter_clockwise __PROTO((BITMAP *)); +extern void bitmap_flip_rotate_clockwise __PROTO((BITMAP *)); +extern void bitmap_flip_rotate_counter_clockwise __PROTO((BITMAP *)); +extern BITMAP *bitmap_convert_lsb8 __PROTO((Uchar *, int, int, int)); +extern BITMAP *bitmap_convert_msb8 __PROTO((Uchar *, int, int, int)); + +#include +extern void bitmap_print __PROTO((FILE *, BITMAP *)); + +#endif /* _BITMAP_H */ diff --git a/backend/dvi/mdvi-lib/color.c b/backend/dvi/mdvi-lib/color.c new file mode 100644 index 00000000..c28107fd --- /dev/null +++ b/backend/dvi/mdvi-lib/color.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "mdvi.h" +#include "color.h" + +void mdvi_set_color(DviContext *dvi, Ulong fg, Ulong bg) +{ + if(dvi->curr_fg != fg || dvi->curr_bg != bg) { + DEBUG((DBG_DEVICE, "setting color to (%lu,%lu)\n", fg, bg)); + if(dvi->device.set_color) + dvi->device.set_color(dvi->device.device_data, fg, bg); + dvi->curr_fg = fg; + dvi->curr_bg = bg; + } +} + +void mdvi_push_color(DviContext *dvi, Ulong fg, Ulong bg) +{ + if(dvi->color_top == dvi->color_size) { + dvi->color_size += 32; + dvi->color_stack = mdvi_realloc(dvi->color_stack, + dvi->color_size * sizeof(DviColorPair)); + } + dvi->color_stack[dvi->color_top].fg = dvi->curr_fg; + dvi->color_stack[dvi->color_top].bg = dvi->curr_bg; + dvi->color_top++; + mdvi_set_color(dvi, fg, bg); +} + +void mdvi_pop_color(DviContext *dvi) +{ + Ulong fg, bg; + + if(dvi->color_top == 0) + return; + dvi->color_top--; + fg = dvi->color_stack[dvi->color_top].fg; + bg = dvi->color_stack[dvi->color_top].bg; + mdvi_set_color(dvi, fg, bg); +} + +void mdvi_reset_color(DviContext *dvi) +{ + dvi->color_top = 0; + mdvi_set_color(dvi, dvi->params.fg, dvi->params.bg); +} + +/* cache for color tables, to avoid creating them for every glyph */ +typedef struct { + Ulong fg; + Ulong bg; + Uint nlevels; + Ulong *pixels; + int density; + double gamma; + Uint hits; +} ColorCache; + +#define CCSIZE 256 +static ColorCache color_cache[CCSIZE]; +static int cc_entries; + +#define GAMMA_DIFF 0.005 + + +/* create a color table */ +Ulong *get_color_table(DviDevice *dev, + int nlevels, Ulong fg, Ulong bg, double gamma, int density) +{ + ColorCache *cc, *tofree; + int lohits; + Ulong *pixels; + int status; + + lohits = color_cache[0].hits; + tofree = &color_cache[0]; + /* look in the cache and see if we have one that matches this request */ + for(cc = &color_cache[0]; cc < &color_cache[cc_entries]; cc++) { + if(cc->hits < lohits) { + lohits = cc->hits; + tofree = cc; + } + if(cc->fg == fg && cc->bg == bg && cc->density == density && + cc->nlevels == nlevels && fabs(cc->gamma - gamma) <= GAMMA_DIFF) + break; + } + + if(cc < &color_cache[cc_entries]) { + cc->hits++; + return cc->pixels; + } + + DEBUG((DBG_DEVICE, "Adding color table to cache (fg=%lu, bg=%lu, n=%d)\n", + fg, bg, nlevels)); + + /* no entry was found in the cache, create a new one */ + if(cc_entries < CCSIZE) { + cc = &color_cache[cc_entries++]; + cc->pixels = NULL; + } else { + cc = tofree; + mdvi_free(cc->pixels); + } + pixels = xnalloc(Ulong, nlevels); + status = dev->alloc_colors(dev->device_data, + pixels, nlevels, fg, bg, gamma, density); + if(status < 0) { + mdvi_free(pixels); + return NULL; + } + cc->fg = fg; + cc->bg = bg; + cc->gamma = gamma; + cc->density = density; + cc->nlevels = nlevels; + cc->pixels = pixels; + cc->hits = 1; + return pixels; +} diff --git a/backend/dvi/mdvi-lib/color.h b/backend/dvi/mdvi-lib/color.h new file mode 100644 index 00000000..35b2f923 --- /dev/null +++ b/backend/dvi/mdvi-lib/color.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + + +#ifndef _COLOR_H_ +#define _COLOR_H_ + +#include "common.h" + +extern Ulong *get_color_table(DviDevice *dev, + int nlevels, Ulong fg, Ulong bg, double gamma, int density); + +extern void mdvi_set_color __PROTO((DviContext *, Ulong, Ulong)); +extern void mdvi_push_color __PROTO((DviContext *, Ulong, Ulong)); +extern void mdvi_pop_color __PROTO((DviContext *)); +extern void mdvi_reset_color __PROTO((DviContext *)); + +#endif /* _COLOR_H_ */ + diff --git a/backend/dvi/mdvi-lib/common.c b/backend/dvi/mdvi-lib/common.c new file mode 100644 index 00000000..97b34b56 --- /dev/null +++ b/backend/dvi/mdvi-lib/common.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include "common.h" + +long fsgetn(FILE *p, size_t n) +{ + long v; + + v = fgetbyte(p); + if(v & 0x80) + v -= 0x100; + while(--n > 0) + v = (v << 8) | fgetbyte(p); + return v; +} + +Ulong fugetn(FILE *p, size_t n) +{ + Ulong v; + + v = fgetbyte(p); + while(--n > 0) + v = (v << 8) | fgetbyte(p); + return v; +} + +long msgetn(const Uchar *p, size_t n) +{ + long v = (long)*p++; + + if(v & 0x80) + v -= 0x100; + while(--n > 0) + v = (v << 8) | *p++; + return v; +} + +Ulong mugetn(const Uchar *p, size_t n) +{ + Ulong v = (Ulong)*p++; + + while(--n > 0) + v = (v << 8) | *p++; + return v; +} + +char *read_string(FILE *in, int s, char *buffer, size_t len) +{ + int n; + char *str; + + n = fugetn(in, s ? s : 1); + if((str = buffer) == NULL || n + 1 > len) + str = mdvi_malloc(n + 1); + if(fread(str, 1, n, in) != n) { + if(str != buffer) mdvi_free(str); + return NULL; + } + str[n] = 0; + return str; +} + +size_t read_bcpl(FILE *in, char *buffer, size_t maxlen, size_t wanted) +{ + size_t i; + + i = (int)fuget1(in); + if(maxlen && i > maxlen) + i = maxlen; + if(fread(buffer, i, 1, in) != 1) + return -1; + buffer[i] = '\0'; + while(wanted-- > i) + (void)fgetc(in); + return i; +} + +char *read_alloc_bcpl(FILE *in, size_t maxlen, size_t *size) +{ + size_t i; + char *buffer; + + i = (size_t)fuget1(in); + if(maxlen && i > maxlen) + i = maxlen; + buffer = (char *)malloc(i + 1); + if(buffer == NULL) + return NULL; + if(fread(buffer, i, 1, in) != 1) { + free(buffer); + return NULL; + } + buffer[i] = '\0'; + if(size) *size = i; + return buffer; +} + +/* buffers */ + +void buff_free(Buffer *buf) +{ + if(buf->data) + mdvi_free(buf->data); + buff_init(buf); +} + +void buff_init(Buffer *buf) +{ + buf->data = NULL; + buf->size = 0; + buf->length = 0; +} + +size_t buff_add(Buffer *buf, const char *data, size_t len) +{ + if(!len && data) + len = strlen(data); + if(buf->length + len + 1 > buf->size) { + buf->size = buf->length + len + 256; + buf->data = mdvi_realloc(buf->data, buf->size); + } + memcpy(buf->data + buf->length, data, len); + buf->length += len; + return buf->length; +} + +char *buff_gets(Buffer *buf, size_t *length) +{ + char *ptr; + char *ret; + size_t len; + + ptr = strchr(buf->data, '\n'); + if(ptr == NULL) + return NULL; + ptr++; /* include newline */ + len = ptr - buf->data; + ret = mdvi_malloc(len + 1); + if(len > 0) { + memcpy(ret, buf->data, len); + memmove(buf->data, buf->data + len, buf->length - len); + buf->length -= len; + } + ret[len] = 0; + if(length) *length = len; + return ret; +} + diff --git a/backend/dvi/mdvi-lib/common.h b/backend/dvi/mdvi-lib/common.h new file mode 100644 index 00000000..27a7d8f9 --- /dev/null +++ b/backend/dvi/mdvi-lib/common.h @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef _MDVI_COMMON_H +#define _MDVI_COMMON_H 1 + +#include +#include +#include + +#include "sysdeps.h" + +#if STDC_HEADERS +# include +#endif + +#if !defined(STDC_HEADERS) || defined(__STRICT_ANSI__) +# ifndef HAVE_STRCHR +# define strchr index +# define strrchr rindex +# endif +# ifndef HAVE_MEMCPY +# define memcpy(a,b,n) bcopy((b), (a), (n)) +# define memmove(a,b,n) bcopy((b), (a), (n)) +# endif +#endif + +#if defined(STDC_HEADERS) || defined(HAVE_MEMCPY) +#define memzero(a,n) memset((a), 0, (n)) +#else +#define memzero(a,n) bzero((a), (n)) +#endif + +typedef struct _List { + struct _List *next; + struct _List *prev; +} List; +#define LIST(x) ((List *)(x)) + +typedef struct { + char *data; + size_t size; + size_t length; +} Buffer; + +typedef struct { + List *head; + List *tail; + int count; +} ListHead; +#define MDVI_EMPTY_LIST_HEAD {NULL, NULL, 0} + +typedef struct { + char *data; + size_t size; + size_t length; +} Dstring; + +/* Functions to read numbers from streams and memory */ + +#define fgetbyte(p) ((unsigned)getc(p)) + +extern char *program_name; + +extern Ulong fugetn __PROTO((FILE *, size_t)); +extern long fsgetn __PROTO((FILE *, size_t)); +extern Ulong mugetn __PROTO((const Uchar *, size_t)); +extern long msgetn __PROTO((const Uchar *, size_t)); + +/* To read from a stream (fu: unsigned, fs: signed) */ +#define fuget4(p) fugetn((p), 4) +#define fuget3(p) fugetn((p), 3) +#define fuget2(p) fugetn((p), 2) +#define fuget1(p) fgetbyte(p) +#define fsget4(p) fsgetn((p), 4) +#define fsget3(p) fsgetn((p), 3) +#define fsget2(p) fsgetn((p), 2) +#define fsget1(p) fsgetn((p), 1) + +/* To read from memory (mu: unsigned, ms: signed) */ +#define MUGETN(p,n) ((p) += (n), mugetn((p)-(n), (n))) +#define MSGETN(p,n) ((p) += (n), msgetn((p)-(n), (n))) +#define muget4(p) MUGETN((p), 4) +#define muget3(p) MUGETN((p), 3) +#define muget2(p) MUGETN((p), 2) +#define muget1(p) MUGETN((p), 1) +#define msget4(p) MSGETN((p), 4) +#define msget3(p) MSGETN((p), 3) +#define msget2(p) MSGETN((p), 2) +#define msget1(p) MSGETN((p), 1) + +#define ROUND(x,y) (((x) + (y) - 1) / (y)) +#define FROUND(x) (int)((x) + 0.5) +#define SFROUND(x) (int)((x) >= 0 ? floor((x) + 0.5) : ceil((x) + 0.5)) + +#define Max(a,b) (((a) > (b)) ? (a) : (b)) +#define Min(a,b) (((a) < (b)) ? (a) : (b)) + +/* make 2byte number from 2 8bit quantities */ +#define HALFWORD(a,b) ((((a) << 8) & 0xf) | (b)) +#define FULLWORD(a,b,c,d) \ + ((((Int8)(a) << 24) & 0xff000000) | \ + (((Uint8)(b) << 16) & 0x00ff0000) | \ + (((Uint8)(c) << 8) & 0x0000ff00) | \ + ((Uint8)(d) & 0xff)) + +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) +#define SWAPINT(a,b) \ + ({ int _s = a; a = b; b = _s; }) +#else +#define SWAPINT(a,b) do { int _s = a; a = b; b = _s; } while(0) +#endif + +#define STREQ(a,b) (strcmp((a), (b)) == 0) +#define STRNEQ(a,b,n) (strncmp((a), (b), (n)) == 0) +#define STRCEQ(a,b) (strcasecmp((a), (b)) == 0) +#define STRNCEQ(a,b,n) (strncasecmp((a), (b), (n)) == 0) + +extern char *read_string __PROTO((FILE *, int, char *, size_t)); +extern size_t read_bcpl __PROTO((FILE *, char *, size_t, size_t)); +extern char *read_alloc_bcpl __PROTO((FILE *, size_t, size_t *)); + +/* miscellaneous */ + +extern void mdvi_message __PROTO((const char *, ...)); +extern void mdvi_crash __PROTO((const char *, ...)); +extern void mdvi_fatal __PROTO((const char *, ...)); +extern void mdvi_error __PROTO((const char *, ...)); +extern void mdvi_warning __PROTO((const char *, ...)); +extern int unit2pix __PROTO((int, const char *)); +extern double unit2pix_factor __PROTO((const char *)); + +#define LOG_NONE -1 +#define LOG_INFO 0 +#define LOG_WARN 1 +#define LOG_ERROR 2 +#define LOG_DEBUG 3 + +#define DBG_OPCODE (1 << 0) +#define DBG_FONTS (1 << 1) +#define DBG_FILES (1 << 2) +#define DBG_DVI (1 << 3) +#define DBG_PARAMS (1 << 4) +#define DBG_SPECIAL (1 << 5) +#define DBG_DEVICE (1 << 6) +#define DBG_GLYPHS (1 << 7) +#define DBG_BITMAPS (1 << 8) +#define DBG_PATHS (1 << 9) +#define DBG_SEARCH (1 << 10) +#define DBG_VARS (1 << 11) +#define DBG_BITMAP_OPS (1 << 12) +#define DBG_BITMAP_DATA (1 << 13) +#define DBG_TYPE1 (1 << 14) +#define DBG_TT (1 << 15) +#define DBG_FT2 (1 << 16) +#define DBG_FMAP (1 << 17) + +#define DBG_SILENT (1 << 31) + +#ifdef NODEBUG +#define DEBUGGING(x) 0 +#else +#define DEBUGGING(x) (_mdvi_debug_mask & DBG_##x) +#endif + +#ifndef NODEBUG +extern Uint32 _mdvi_debug_mask; +extern void __debug __PROTO((int, const char *, ...)); +#define DEBUG(x) __debug x +#define ASSERT(x) do { \ + if(!(x)) mdvi_crash("%s:%d: Assertion %s failed\n", \ + __FILE__, __LINE__, #x); \ + } while(0) +#define ASSERT_VALUE(x,y) do { \ + if((x) != (y)) \ + mdvi_crash("%s:%d: Assertion failed (%d = %s != %s)\n", \ + __FILE__, __LINE__, (x), #x, #x); \ + } while(0) +#else +#define DEBUG(x) do { } while(0) +#define ASSERT(x) do { } while(0) +#define ASSERT_VALUE(x,y) do { } while(0) +#endif + +#define set_debug_mask(m) (_mdvi_debug_mask = (Uint32)(m)) +#define add_debug_mask(m) (_mdvi_debug_mask |= (Uint32)(m)) +#define get_debug_mask() _mdvi_debug_mask + +/* memory allocation */ + +extern void mdvi_free __PROTO((void *)); +extern void *mdvi_malloc __PROTO((size_t)); +extern void *mdvi_realloc __PROTO((void *, size_t)); +extern void *mdvi_calloc __PROTO((size_t, size_t)); +extern char *mdvi_strncpy __PROTO((char *, const char *, size_t)); +extern char *mdvi_strdup __PROTO((const char *)); +extern char *mdvi_strndup __PROTO((const char *, size_t)); +extern void *mdvi_memdup __PROTO((const void *, size_t)); +extern char *mdvi_build_path_from_cwd __PROTO((const char *)); +extern char *mdvi_strrstr __PROTO((const char *, const char *)); + +/* macros to make memory allocation nicer */ +#define xalloc(t) (t *)mdvi_malloc(sizeof(t)) +#define xnalloc(t,n) (t *)mdvi_calloc((n), sizeof(t)) +#define xresize(p,t,n) (t *)mdvi_realloc((p), (n) * sizeof(t)) + +extern char *xstradd __PROTO((char *, size_t *, size_t, const char *, size_t)); + +extern Ulong get_mtime __PROTO((int)); + +/* lists */ +extern void listh_init __PROTO((ListHead *)); +extern void listh_prepend __PROTO((ListHead *, List *)); +extern void listh_append __PROTO((ListHead *, List *)); +extern void listh_add_before __PROTO((ListHead *, List *, List *)); +extern void listh_add_after __PROTO((ListHead *, List *, List *)); +extern void listh_remove __PROTO((ListHead *, List *)); +extern void listh_concat __PROTO((ListHead *, ListHead *)); +extern void listh_catcon __PROTO((ListHead *, ListHead *)); + +extern void buff_init __PROTO((Buffer *)); +extern size_t buff_add __PROTO((Buffer *, const char *, size_t)); +extern char *buff_gets __PROTO((Buffer *, size_t *)); +extern void buff_free __PROTO((Buffer *)); + +extern char *getword __PROTO((char *, const char *, char **)); +extern char *getstring __PROTO((char *, const char *, char **)); + +extern void dstring_init __PROTO((Dstring *)); +extern int dstring_new __PROTO((Dstring *, const char *, int)); +extern int dstring_append __PROTO((Dstring *, const char *, int)); +extern int dstring_copy __PROTO((Dstring *, int, const char *, int)); +extern int dstring_insert __PROTO((Dstring *, int, const char *, int)); +extern void dstring_reset __PROTO((Dstring *)); + +#define dstring_length(d) ((d)->length) +#define dstring_strcat(d,s) dstring_append((d), (s), -1) + +extern char *dgets __PROTO((Dstring *, FILE *)); +extern int file_readable __PROTO((const char *)); +extern int file_exists __PROTO((const char *)); +extern const char *file_basename __PROTO((const char *)); +extern const char *file_extension __PROTO((const char *)); + +/* + * Miscellaneous macros + */ + +#define LIST_FOREACH(ptr, type, list) \ + for(ptr = (type *)(list)->head; ptr; ptr = (ptr)->next) + +#define Size(x) (sizeof(x) / sizeof((x)[0])) + +/* multiply a fix_word by a 32bit number */ +#define B0(x) ((x) & 0xff) +#define B1(x) B0((x) >> 8) +#define B2(x) B0((x) >> 16) +#define B3(x) B0((x) >> 24) +#define __tfm_mul(z,t) \ + (((((B0(t) * (z)) >> 8) + (B1(t) * (z))) >> 8) + B2(t) * (z)) +#define TFMSCALE(z,t,a,b) \ + ((B3(t) == 255) ? \ + __tfm_mul((z), (t)) / (b) - (a) : \ + __tfm_mul((z), (t)) / (b)) +#define TFMPREPARE(x,z,a,b) do { \ + a = 16; z = (x); \ + while(z > 040000000L) { z >>= 1; a <<= 1; } \ + b = 256 / a; a *= z; \ + } while(0) + +#endif /* _MDVI_COMMON_H */ diff --git a/backend/dvi/mdvi-lib/defaults.h b/backend/dvi/mdvi-lib/defaults.h new file mode 100644 index 00000000..7e63f81e --- /dev/null +++ b/backend/dvi/mdvi-lib/defaults.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef _MDVI_DEFAULTS_H +#define _MDVI_DEFAULTS_H 1 + +/* resolution */ +#define MDVI_DPI 600 +#define MDVI_VDPI MDVI_DPI + +/* horizontal margins */ +#define MDVI_HMARGIN "1in" + +/* vertical margins */ +#define MDVI_VMARGIN "1in" + +/* rulers */ +#define MDVI_HRUNITS "1in" +#define MDVI_VRUNITS "1in" + +/* paper */ +#define MDVI_PAPERNAME "letter" + +/* magnification */ +#define MDVI_MAGNIFICATION 1.0 + +/* fallback font */ +#define MDVI_FALLBACK_FONT "cmr10" + +/* metafont mode */ +#define MDVI_MFMODE NULL + +/* default shrinking factor */ +#define MDVI_DEFAULT_SHRINKING -1 /* based on resolution */ + +/* default pixel density */ +#define MDVI_DEFAULT_DENSITY 50 + +/* default gamma correction */ +#define MDVI_DEFAULT_GAMMA 1.0 + +/* default window geometry */ +#define MDVI_GEOMETRY NULL + +/* default orientation */ +#define MDVI_ORIENTATION "tblr" + +/* colors */ +#define MDVI_FOREGROUND "black" +#define MDVI_BACKGROUND "white" + +/* flags */ +#define MDVI_DEFAULT_FLAGS MDVI_ANTIALIASED + +#define MDVI_DEFAULT_CONFIG "mdvi.conf" + +#endif /* _MDVI_DEAFAULTS_H */ diff --git a/backend/dvi/mdvi-lib/dviopcodes.h b/backend/dvi/mdvi-lib/dviopcodes.h new file mode 100644 index 00000000..f99af05e --- /dev/null +++ b/backend/dvi/mdvi-lib/dviopcodes.h @@ -0,0 +1,72 @@ +#ifndef _MDVI_DVIOPCODES_H +#define _MDVI_DVIOPCODES_H 1 + +#define DVI_SET_CHAR0 0 +#define DVI_SET_CHAR1 1 +#define DVI_SET_CHAR_MAX 127 +#define DVI_SET1 128 +#define DVI_SET2 129 +#define DVI_SET3 130 +#define DVI_SET4 131 +#define DVI_SET_RULE 132 +#define DVI_PUT1 133 +#define DVI_PUT2 134 +#define DVI_PUT3 135 +#define DVI_PUT4 136 +#define DVI_PUT_RULE 137 +#define DVI_NOOP 138 +#define DVI_BOP 139 +#define DVI_EOP 140 +#define DVI_PUSH 141 +#define DVI_POP 142 +#define DVI_RIGHT1 143 +#define DVI_RIGHT2 144 +#define DVI_RIGHT3 145 +#define DVI_RIGHT4 146 +#define DVI_W0 147 +#define DVI_W1 148 +#define DVI_W2 149 +#define DVI_W3 150 +#define DVI_W4 151 +#define DVI_X0 152 +#define DVI_X1 153 +#define DVI_X2 154 +#define DVI_X3 155 +#define DVI_X4 156 +#define DVI_DOWN1 157 +#define DVI_DOWN2 158 +#define DVI_DOWN3 159 +#define DVI_DOWN4 160 +#define DVI_Y0 161 +#define DVI_Y1 162 +#define DVI_Y2 163 +#define DVI_Y3 164 +#define DVI_Y4 165 +#define DVI_Z0 166 +#define DVI_Z1 167 +#define DVI_Z2 168 +#define DVI_Z3 169 +#define DVI_Z4 170 +#define DVI_FNT_NUM0 171 +#define DVI_FNT_NUM1 172 +#define DVI_FNT_NUM_MAX 234 +#define DVI_FNT1 235 +#define DVI_FNT2 236 +#define DVI_FNT3 237 +#define DVI_FNT4 238 +#define DVI_XXX1 239 +#define DVI_XXX2 240 +#define DVI_XXX3 241 +#define DVI_XXX4 242 +#define DVI_FNT_DEF1 243 +#define DVI_FNT_DEF2 244 +#define DVI_FNT_DEF3 245 +#define DVI_FNT_DEF4 246 +#define DVI_PRE 247 +#define DVI_POST 248 +#define DVI_POST_POST 249 + +#define DVI_ID 2 +#define DVI_TRAILER 223 + +#endif /* _MDVI_DVIOPCODES_H */ diff --git a/backend/dvi/mdvi-lib/dviread.c b/backend/dvi/mdvi-lib/dviread.c new file mode 100644 index 00000000..97b7b844 --- /dev/null +++ b/backend/dvi/mdvi-lib/dviread.c @@ -0,0 +1,1585 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdvi.h" +#include "private.h" +#include "color.h" + +typedef int (*DviCommand) __PROTO((DviContext *, int)); + +#define DVICMDDEF(x) static int x __PROTO((DviContext *, int)) + +DVICMDDEF(set_char); +DVICMDDEF(set_rule); +DVICMDDEF(no_op); +DVICMDDEF(push); +DVICMDDEF(pop); +DVICMDDEF(move_right); +DVICMDDEF(move_down); +DVICMDDEF(move_w); +DVICMDDEF(move_x); +DVICMDDEF(move_y); +DVICMDDEF(move_z); +DVICMDDEF(sel_font); +DVICMDDEF(sel_fontn); +DVICMDDEF(special); +DVICMDDEF(def_font); +DVICMDDEF(undefined); +DVICMDDEF(unexpected); + +static const DviCommand dvi_commands[] = { + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, + set_char, set_char, set_char, set_char, /* 0 - 127 */ + set_char, set_char, set_char, set_char, /* 128 - 131 */ + set_rule, /* 132 */ + set_char, set_char, set_char, set_char, /* 133 - 136 */ + set_rule, /* 137 */ + no_op, /* 138 */ + unexpected, /* 139 (BOP) */ + unexpected, /* 140 (EOP) */ + push, /* 141 */ + pop, /* 142 */ + move_right, move_right, move_right, move_right, /* 143 - 146 */ + move_w, move_w, move_w, move_w, move_w, /* 147 - 151 */ + move_x, move_x, move_x, move_x, move_x, /* 152 - 156 */ + move_down, move_down, move_down, move_down, /* 157 - 160 */ + move_y, move_y, move_y, move_y, move_y, /* 161 - 165 */ + move_z, move_z, move_z, move_z, move_z, /* 166 - 170 */ + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, + sel_font, sel_font, sel_font, sel_font, /* 171 - 234 */ + sel_fontn, sel_fontn, sel_fontn, sel_fontn, /* 235 - 238 */ + special, special, special, special, /* 239 - 242 */ + def_font, def_font, def_font, def_font, /* 243 - 246 */ + unexpected, /* 247 (PRE) */ + unexpected, /* 248 (POST) */ + unexpected, /* 249 (POST_POST) */ + undefined, undefined, undefined, + undefined, undefined, undefined /* 250 - 255 */ +}; + +#define DVI_BUFLEN 4096 + +static int mdvi_run_macro(DviContext *dvi, Uchar *macro, size_t len); + +static void dummy_draw_glyph(DviContext *dvi, DviFontChar *ch, int x, int y) +{ +} + +static void dummy_draw_rule(DviContext *dvi, int x, int y, Uint w, Uint h, int f) +{ +} + +static int dummy_alloc_colors(void *a, Ulong *b, int c, Ulong d, Ulong e, double f, int g) +{ + return -1; +} + +static void *dummy_create_image(void *a, Uint b, Uint c, Uint d) +{ + return NULL; +} + +static void dummy_free_image(void *a) +{ +} + +static void dummy_dev_destroy(void *a) +{ +} + +static void dummy_dev_putpixel(void *a, int x, int y, Ulong c) +{ +} + +static void dummy_dev_refresh(DviContext *a, void *b) +{ +} + +static void dummy_dev_set_color(void *a, Ulong b, Ulong c) +{ +} + +/* functions to report errors */ +static void dvierr(DviContext *dvi, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + fprintf(stderr, "%s[%d]: Error: ", + dvi->filename, dvi->currpage); + vfprintf(stderr, format, ap); + va_end(ap); +} + +static void dviwarn(DviContext *dvi, const char *format, ...) +{ + va_list ap; + + fprintf(stderr, "%s[%d]: Warning: ", + dvi->filename, dvi->currpage); + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} + +#define NEEDBYTES(d,n) \ + ((d)->buffer.pos + (n) > (d)->buffer.length) + +static int get_bytes(DviContext *dvi, size_t n) +{ + /* + * caller wants to read `n' bytes from dvi->buffer + dvi->pos. + * Make sure there is enough data to satisfy the request + */ + if(NEEDBYTES(dvi, n)) { + size_t required; + int newlen; + + if(dvi->buffer.frozen || dvi->in == NULL || feof(dvi->in)) { + /* this is EOF */ + dviwarn(dvi, _("unexpected EOF\n")); + return -1; + } + /* get more data */ + if(dvi->buffer.data == NULL) { + /* first allocation */ + dvi->buffer.size = Max(DVI_BUFLEN, n); + dvi->buffer.data = (Uchar *)mdvi_malloc(dvi->buffer.size); + dvi->buffer.length = 0; + dvi->buffer.frozen = 0; + } else if(dvi->buffer.pos < dvi->buffer.length) { + /* move whatever we want to keep */ + dvi->buffer.length -= dvi->buffer.pos; + memmove(dvi->buffer.data, + dvi->buffer.data + dvi->buffer.pos, + dvi->buffer.length); + } else { + /* we can discard all the data in this buffer */ + dvi->buffer.length = 0; + } + + required = n - dvi->buffer.length; + if(required > dvi->buffer.size - dvi->buffer.length) { + /* need to allocate more memory */ + dvi->buffer.size = dvi->buffer.length + required + 128; + dvi->buffer.data = (Uchar *)xresize(dvi->buffer.data, + char, dvi->buffer.size); + } + /* now read into the buffer */ + newlen = fread(dvi->buffer.data + dvi->buffer.length, + 1, dvi->buffer.size - dvi->buffer.length, dvi->in); + if(newlen == -1) { + mdvi_error("%s: %s\n", dvi->filename, strerror(errno)); + return -1; + } + dvi->buffer.length += newlen; + dvi->buffer.pos = 0; + } + return 0; +} + +/* only relative forward seeks are supported by this function */ +static int dskip(DviContext *dvi, long offset) +{ + ASSERT(offset > 0); + + if(NEEDBYTES(dvi, offset) && get_bytes(dvi, offset) == -1) + return -1; + dvi->buffer.pos += offset; + return 0; +} + +/* DVI I/O functions (note: here `n' must be <= 4) */ +static long dsgetn(DviContext *dvi, size_t n) +{ + long val; + + if(NEEDBYTES(dvi, n) && get_bytes(dvi, n) == -1) + return -1; + val = msgetn(dvi->buffer.data + dvi->buffer.pos, n); + dvi->buffer.pos += n; + return val; +} + +static int dread(DviContext *dvi, char *buffer, size_t len) +{ + if(NEEDBYTES(dvi, len) && get_bytes(dvi, len) == -1) + return -1; + memcpy(buffer, dvi->buffer.data + dvi->buffer.pos, len); + dvi->buffer.pos += len; + return 0; +} + +static long dugetn(DviContext *dvi, size_t n) +{ + long val; + + if(NEEDBYTES(dvi, n) && get_bytes(dvi, n) == -1) + return -1; + val = mugetn(dvi->buffer.data + dvi->buffer.pos, n); + dvi->buffer.pos += n; + return val; +} + +static long dtell(DviContext *dvi) +{ + return dvi->depth ? + dvi->buffer.pos : + ftell(dvi->in) - dvi->buffer.length + dvi->buffer.pos; +} + +static void dreset(DviContext *dvi) +{ + if(!dvi->buffer.frozen && dvi->buffer.data) + mdvi_free(dvi->buffer.data); + dvi->buffer.data = NULL; + dvi->buffer.size = 0; + dvi->buffer.length = 0; + dvi->buffer.pos = 0; +} + +#define dsget1(d) dsgetn((d), 1) +#define dsget2(d) dsgetn((d), 2) +#define dsget3(d) dsgetn((d), 3) +#define dsget4(d) dsgetn((d), 4) +#define duget1(d) dugetn((d), 1) +#define duget2(d) dugetn((d), 2) +#define duget3(d) dugetn((d), 3) +#define duget4(d) dugetn((d), 4) + +#ifndef NODEBUG +static void dviprint(DviContext *dvi, const char *command, int sub, const char *fmt, ...) +{ + int i; + va_list ap; + + printf("%s: ", dvi->filename); + for(i = 0; i < dvi->depth; i++) + printf(" "); + printf("%4lu: %s", dtell(dvi), command); + if(sub >= 0) printf("%d", sub); + if(*fmt) printf(": "); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} +#define SHOWCMD(x) \ + if(_mdvi_debug_mask & DBG_OPCODE) do { dviprint x; } while(0) +#else +#define SHOWCMD(x) do { } while(0) +#endif + +int mdvi_find_tex_page(DviContext *dvi, int tex_page) +{ + int i; + + for(i = 0; i < dvi->npages; i++) + if(dvi->pagemap[i][1] == tex_page) + return i; + return -1; +} + +/* page sorting functions */ +static int sort_up(const void *p1, const void *p2) +{ + return ((long *)p1)[1] - ((long *)p2)[1]; +} +static int sort_down(const void *p1, const void *p2) +{ + return ((long *)p2)[1] - ((long *)p1)[1]; +} +static int sort_random(const void *p1, const void *p2) +{ + return (rand() % 1) ? -1 : 1; +} +static int sort_dvi_up(const void *p1, const void *p2) +{ + return ((long *)p1)[0] - ((long *)p2)[0]; +} +static int sort_dvi_down(const void *p1, const void *p2) +{ + return ((long *)p1)[0] - ((long *)p2)[0]; +} + +void mdvi_sort_pages(DviContext *dvi, DviPageSort type) +{ + int (*sortfunc) __PROTO((const void *, const void *)); + + switch(type) { + case MDVI_PAGE_SORT_UP: + sortfunc = sort_up; + break; + case MDVI_PAGE_SORT_DOWN: + sortfunc = sort_down; + break; + case MDVI_PAGE_SORT_RANDOM: + sortfunc = sort_random; + break; + case MDVI_PAGE_SORT_DVI_UP: + sortfunc = sort_dvi_up; + break; + case MDVI_PAGE_SORT_DVI_DOWN: + sortfunc = sort_dvi_down; + break; + case MDVI_PAGE_SORT_NONE: + default: + sortfunc = NULL; + break; + } + + if(sortfunc) + qsort(dvi->pagemap, dvi->npages, sizeof(PageNum), sortfunc); +} + +static DviFontRef *define_font(DviContext *dvi, int op) +{ + Int32 arg; + Int32 scale; + Int32 dsize; + Int32 checksum; + int hdpi; + int vdpi; + int n; + char *name; + DviFontRef *ref; + + arg = dugetn(dvi, op - DVI_FNT_DEF1 + 1); + checksum = duget4(dvi); + scale = duget4(dvi); + dsize = duget4(dvi); + hdpi = FROUND(dvi->params.mag * dvi->params.dpi * scale / dsize); + vdpi = FROUND(dvi->params.mag * dvi->params.vdpi * scale / dsize); + n = duget1(dvi) + duget1(dvi); + name = mdvi_malloc(n + 1); + dread(dvi, name, n); + name[n] = 0; + DEBUG((DBG_FONTS, "requesting font %d = `%s' at %.1fpt (%dx%d dpi)\n", + arg, name, (double)scale / (dvi->params.tfm_conv * 0x100000), + hdpi, vdpi)); + ref = font_reference(&dvi->params, arg, name, checksum, hdpi, vdpi, scale); + if(ref == NULL) { + mdvi_error(_("could not load font `%s'\n"), name); + mdvi_free(name); + return NULL; + } + mdvi_free(name); + return ref; +} + +static char *opendvi(const char *name) +{ + int len; + char *file; + + len = strlen(name); + /* if file ends with .dvi and it exists, that's it */ + if(len >= 4 && STREQ(name+len-4, ".dvi")) { + DEBUG((DBG_DVI|DBG_FILES, "opendvi: Trying `%s'\n", name)); + if(access(name, R_OK) == 0) + return mdvi_strdup(name); + } + + /* try appending .dvi */ + file = mdvi_malloc(len + 5); + strcpy(file, name); + strcpy(file+len, ".dvi"); + DEBUG((DBG_DVI|DBG_FILES, "opendvi: Trying `%s'\n", file)); + if(access(file, R_OK) == 0) + return file; + /* try the given name */ + file[len] = 0; + DEBUG((DBG_DVI|DBG_FILES, "opendvi: Trying `%s'\n", file)); + if(access(file, R_OK) == 0) + return file; + mdvi_free(file); + return NULL; +} + +int mdvi_reload(DviContext *dvi, DviParams *np) +{ + DviContext *newdvi; + DviParams *pars; + + /* close our file */ + if(dvi->in) { + fclose(dvi->in); + dvi->in = NULL; + } + + pars = np ? np : &dvi->params; + DEBUG((DBG_DVI, "%s: reloading\n", dvi->filename)); + + /* load it again */ + newdvi = mdvi_init_context(pars, dvi->pagesel, dvi->filename); + if(newdvi == NULL) { + mdvi_warning(_("could not reload `%s'\n"), dvi->filename); + return -1; + } + + /* drop all our font references */ + font_drop_chain(dvi->fonts); + /* destroy our font map */ + if(dvi->fontmap) + mdvi_free(dvi->fontmap); + dvi->currfont = NULL; + + /* and use the ones we just loaded */ + dvi->fonts = newdvi->fonts; + dvi->fontmap = newdvi->fontmap; + dvi->nfonts = newdvi->nfonts; + + /* copy the new information */ + dvi->params = newdvi->params; + dvi->num = newdvi->num; + dvi->den = newdvi->den; + dvi->dvimag = newdvi->dvimag; + dvi->dviconv = newdvi->dviconv; + dvi->dvivconv = newdvi->dvivconv; + dvi->modtime = newdvi->modtime; + + if(dvi->fileid) mdvi_free(dvi->fileid); + dvi->fileid = newdvi->fileid; + + dvi->dvi_page_w = newdvi->dvi_page_w; + dvi->dvi_page_h = newdvi->dvi_page_h; + + mdvi_free(dvi->pagemap); + dvi->pagemap = newdvi->pagemap; + dvi->npages = newdvi->npages; + if(dvi->currpage > dvi->npages-1) + dvi->currpage = 0; + + mdvi_free(dvi->stack); + dvi->stack = newdvi->stack; + dvi->stacksize = newdvi->stacksize; + + /* remove fonts that are not being used anymore */ + font_free_unused(&dvi->device); + + mdvi_free(newdvi->filename); + mdvi_free(newdvi); + + DEBUG((DBG_DVI, "%s: reload successful\n", dvi->filename)); + if(dvi->device.refresh) + dvi->device.refresh(dvi, dvi->device.device_data); + + return 0; +} + +/* function to change parameters ia DVI context + * The DVI context is modified ONLY if this function is successful */ +int mdvi_configure(DviContext *dvi, DviParamCode option, ...) +{ + va_list ap; + int reset_all; + int reset_font; + DviParams np; + + va_start(ap, option); + + reset_font = 0; + reset_all = 0; + np = dvi->params; /* structure copy */ + while(option != MDVI_PARAM_LAST) { + switch(option) { + case MDVI_SET_DPI: + np.dpi = np.vdpi = va_arg(ap, Uint); + reset_all = 1; + break; + case MDVI_SET_XDPI: + np.dpi = va_arg(ap, Uint); + reset_all = 1; + break; + case MDVI_SET_YDPI: + np.vdpi = va_arg(ap, Uint); + break; + case MDVI_SET_SHRINK: + np.hshrink = np.vshrink = va_arg(ap, Uint); + reset_font = MDVI_FONTSEL_GREY|MDVI_FONTSEL_BITMAP; + break; + case MDVI_SET_XSHRINK: + np.hshrink = va_arg(ap, Uint); + reset_font = MDVI_FONTSEL_GREY|MDVI_FONTSEL_BITMAP; + break; + case MDVI_SET_YSHRINK: + np.vshrink = va_arg(ap, Uint); + reset_font = MDVI_FONTSEL_GREY|MDVI_FONTSEL_BITMAP; + break; + case MDVI_SET_ORIENTATION: + np.orientation = va_arg(ap, DviOrientation); + reset_font = MDVI_FONTSEL_GLYPH; + break; + case MDVI_SET_GAMMA: + np.gamma = va_arg(ap, double); + reset_font = MDVI_FONTSEL_GREY; + break; + case MDVI_SET_DENSITY: + np.density = va_arg(ap, Uint); + reset_font = MDVI_FONTSEL_BITMAP; + break; + case MDVI_SET_MAGNIFICATION: + np.mag = va_arg(ap, double); + reset_all = 1; + break; + case MDVI_SET_DRIFT: + np.hdrift = np.vdrift = va_arg(ap, int); + break; + case MDVI_SET_HDRIFT: + np.hdrift = va_arg(ap, int); + break; + case MDVI_SET_VDRIFT: + np.vdrift = va_arg(ap, int); + break; + case MDVI_SET_FOREGROUND: + np.fg = va_arg(ap, Ulong); + reset_font = MDVI_FONTSEL_GREY; + break; + case MDVI_SET_BACKGROUND: + np.bg = va_arg(ap, Ulong); + reset_font = MDVI_FONTSEL_GREY; + break; + default: + break; + } + option = va_arg(ap, DviParamCode); + } + va_end(ap); + + /* check that all values make sense */ + if(np.dpi <= 0 || np.vdpi <= 0) + return -1; + if(np.mag <= 0.0) + return -1; + if(np.density < 0) + return -1; + if(np.hshrink < 1 || np.vshrink < 1) + return -1; + if(np.hdrift < 0 || np.vdrift < 0) + return -1; + if(np.fg == np.bg) + return -1; + + /* + * If the dpi or the magnification change, we basically have to reload + * the DVI file again from scratch. + */ + + if(reset_all) + return (mdvi_reload(dvi, &np) == 0); + + if(np.hshrink != dvi->params.hshrink) { + np.conv = dvi->dviconv; + if(np.hshrink) + np.conv /= np.hshrink; + } + if(np.vshrink != dvi->params.vshrink) { + np.vconv = dvi->dvivconv; + if(np.vshrink) + np.vconv /= np.vshrink; + } + + if(reset_font) { + font_reset_chain_glyphs(&dvi->device, dvi->fonts, reset_font); + } + dvi->params = np; + if((reset_font & MDVI_FONTSEL_GLYPH) && dvi->device.refresh) { + dvi->device.refresh(dvi, dvi->device.device_data); + return 0; + } + + return 1; +} +/* + * Read the initial data from the DVI file. If something is wrong with the + * file, we just spit out an error message and refuse to load the file, + * without giving any details. This makes sense because DVI files are ok + * 99.99% of the time, and dvitype(1) can be used to check the other 0.01%. + */ +DviContext *mdvi_init_context(DviParams *par, DviPageSpec *spec, const char *file) +{ + FILE *p; + Int32 arg; + int op; + long offset; + int n; + DviContext *dvi; + char *filename; + int pagecount; + + /* + * 1. Open the file and initialize the DVI context + */ + + filename = opendvi(file); + if(filename == NULL) { + perror(file); + return NULL; + } + p = fopen(filename, "rb"); + if(p == NULL) { + perror(file); + mdvi_free(filename); + return NULL; + } + dvi = xalloc(DviContext); + memzero(dvi, sizeof(DviContext)); + dvi->pagemap = NULL; + dvi->filename = filename; + dvi->stack = NULL; + dvi->modtime = get_mtime(fileno(p)); + dvi->buffer.data = NULL; + dvi->pagesel = spec; + dvi->in = p; /* now we can use the dget*() functions */ + + /* + * 2. Read the preamble, extract scaling information, and + * setup the DVI parameters. + */ + + if(fuget1(p) != DVI_PRE) + goto bad_dvi; + if((arg = fuget1(p)) != DVI_ID) { + mdvi_error(_("%s: unsupported DVI format (version %u)\n"), + file, arg); + goto error; /* jump to the end of this routine, + * where we handle errors */ + } + /* get dimensions */ + dvi->num = fuget4(p); + dvi->den = fuget4(p); + dvi->dvimag = fuget4(p); + + /* check that these numbers make sense */ + if(!dvi->num || !dvi->den || !dvi->dvimag) + goto bad_dvi; + + dvi->params.mag = + (par->mag > 0 ? par->mag : (double)dvi->dvimag / 1000.0); + dvi->params.hdrift = par->hdrift; + dvi->params.vdrift = par->vdrift; + dvi->params.dpi = par->dpi ? par->dpi : MDVI_DPI; + dvi->params.vdpi = par->vdpi ? par->vdpi : par->dpi; + dvi->params.hshrink = par->hshrink; + dvi->params.vshrink = par->vshrink; + dvi->params.density = par->density; + dvi->params.gamma = par->gamma; + dvi->params.conv = (double)dvi->num / dvi->den; + dvi->params.conv *= (dvi->params.dpi / 254000.0) * dvi->params.mag; + dvi->params.vconv = (double)dvi->num / dvi->den; + dvi->params.vconv *= (dvi->params.vdpi / 254000.0) * dvi->params.mag; + dvi->params.tfm_conv = (25400000.0 / dvi->num) * + ((double)dvi->den / 473628672) / 16.0; + dvi->params.flags = par->flags; + dvi->params.orientation = par->orientation; + dvi->params.fg = par->fg; + dvi->params.bg = par->bg; + + /* initialize colors */ + dvi->curr_fg = par->fg; + dvi->curr_bg = par->bg; + dvi->color_stack = NULL; + dvi->color_top = 0; + dvi->color_size = 0; + + /* pixel conversion factors */ + dvi->dviconv = dvi->params.conv; + dvi->dvivconv = dvi->params.vconv; + if(dvi->params.hshrink) + dvi->params.conv /= dvi->params.hshrink; + if(dvi->params.vshrink) + dvi->params.vconv /= dvi->params.vshrink; + + /* get the comment from the preamble */ + n = fuget1(p); + dvi->fileid = mdvi_malloc(n + 1); + fread(dvi->fileid, 1, n, p); + dvi->fileid[n] = 0; + DEBUG((DBG_DVI, "%s: %s\n", filename, dvi->fileid)); + + /* + * 3. Read postamble, extract page information (number of + * pages, dimensions) and stack depth. + */ + + /* jump to the end of the file */ + if(fseek(p, (long)-1, SEEK_END) == -1) + goto error; + for(n = 0; (op = fuget1(p)) == DVI_TRAILER; n++) + if(fseek(p, (long)-2, SEEK_CUR) < 0) + break; + if(op != arg || n < 4) + goto bad_dvi; + /* get the pointer to postamble */ + fseek(p, (long)-5, SEEK_CUR); + arg = fuget4(p); + /* jump to it */ + fseek(p, (long)arg, SEEK_SET); + if(fuget1(p) != DVI_POST) + goto bad_dvi; + offset = fuget4(p); + if(dvi->num != fuget4(p) || dvi->den != fuget4(p) || + dvi->dvimag != fuget4(p)) + goto bad_dvi; + dvi->dvi_page_h = fuget4(p); + dvi->dvi_page_w = fuget4(p); + dvi->stacksize = fuget2(p); + dvi->npages = fuget2(p); + DEBUG((DBG_DVI, "%s: from postamble: stack depth %d, %d page%s\n", + filename, dvi->stacksize, dvi->npages, dvi->npages > 1 ? "s" : "")); + + /* + * 4. Process font definitions. + */ + + /* process font definitions */ + dvi->nfonts = 0; + dvi->fontmap = NULL; + /* + * CAREFUL: here we need to use the dvi->buffer, but it might leave the + * the file cursor in the wrong position after reading fonts (because of + * buffering). It's ok, though, because after the font definitions we read + * the page offsets, and we fseek() to the relevant part of the file with + * SEEK_SET. Nothing is read after the page offsets. + */ + while((op = duget1(dvi)) != DVI_POST_POST) { + DviFontRef *ref; + + if(op == DVI_NOOP) + continue; + else if(op < DVI_FNT_DEF1 || op > DVI_FNT_DEF4) + goto error; + ref = define_font(dvi, op); + if(ref == NULL) + goto error; + ref->next = dvi->fonts; + dvi->fonts = ref; + dvi->nfonts++; + } + /* we don't need the buffer anymore */ + dreset(dvi); + + if(op != DVI_POST_POST) + goto bad_dvi; + font_finish_definitions(dvi); + DEBUG((DBG_DVI, "%s: %d font%s required by this job\n", + filename, dvi->nfonts, dvi->nfonts > 1 ? "s" : "")); + dvi->findref = font_find_mapped; + + /* + * 5. Build the page map. + */ + + dvi->pagemap = xnalloc(PageNum, dvi->npages); + memzero(dvi->pagemap, sizeof(PageNum) * dvi->npages); + + n = dvi->npages - 1; + pagecount = n; + while(offset != -1) { + int i; + PageNum page; + + fseek(p, offset, SEEK_SET); + op = fuget1(p); + if(op != DVI_BOP || n < 0) + goto bad_dvi; + for(i = 1; i <= 10; i++) + page[i] = fsget4(p); + page[0] = offset; + offset = fsget4(p); + /* check if the page is selected */ + if(spec && mdvi_page_selected(spec, page, n) == 0) { + DEBUG((DBG_DVI, "Page %d (%ld.%ld.%ld.%ld.%ld.%ld.%ld.%ld.%ld.%ld) ignored by request\n", + n, page[1], page[2], page[3], page[4], page[5], + page[6], page[7], page[8], page[9], page[10])); + } else { + memcpy(&dvi->pagemap[pagecount], page, sizeof(PageNum)); + pagecount--; + } + n--; + } + pagecount++; + if(pagecount >= dvi->npages) { + mdvi_error(_("no pages selected\n")); + goto error; + } + if(pagecount) { + DEBUG((DBG_DVI, "%d of %d pages selected\n", + dvi->npages - pagecount, dvi->npages)); + dvi->npages -= pagecount; + memmove(dvi->pagemap, &dvi->pagemap[pagecount], + dvi->npages * sizeof(PageNum)); + } + + /* + * 6. Setup stack, initialize device functions + */ + + dvi->curr_layer = 0; + dvi->stack = xnalloc(DviState, dvi->stacksize + 8); + + dvi->device.draw_glyph = dummy_draw_glyph; + dvi->device.draw_rule = dummy_draw_rule; + dvi->device.alloc_colors = dummy_alloc_colors; + dvi->device.create_image = dummy_create_image; + dvi->device.free_image = dummy_free_image; + dvi->device.dev_destroy = dummy_dev_destroy; + dvi->device.put_pixel = dummy_dev_putpixel; + dvi->device.refresh = dummy_dev_refresh; + dvi->device.set_color = dummy_dev_set_color; + dvi->device.device_data = NULL; + + DEBUG((DBG_DVI, "%s read successfully\n", filename)); + return dvi; + +bad_dvi: + mdvi_error(_("%s: File corrupted, or not a DVI file\n"), file); +error: + /* if we came from the font definitions, this will be non-trivial */ + dreset(dvi); + mdvi_destroy_context(dvi); + return NULL; +} + +void mdvi_destroy_context(DviContext *dvi) +{ + if(dvi->device.dev_destroy) + dvi->device.dev_destroy(dvi->device.device_data); + /* release all fonts */ + if(dvi->fonts) { + font_drop_chain(dvi->fonts); + font_free_unused(&dvi->device); + } + if(dvi->fontmap) + mdvi_free(dvi->fontmap); + if(dvi->filename) + mdvi_free(dvi->filename); + if(dvi->stack) + mdvi_free(dvi->stack); + if(dvi->pagemap) + mdvi_free(dvi->pagemap); + if(dvi->fileid) + mdvi_free(dvi->fileid); + if(dvi->in) + fclose(dvi->in); + if(dvi->buffer.data && !dvi->buffer.frozen) + mdvi_free(dvi->buffer.data); + if(dvi->color_stack) + mdvi_free(dvi->color_stack); + + mdvi_free(dvi); +} + +void mdvi_setpage(DviContext *dvi, int pageno) +{ + if(pageno < 0) + pageno = 0; + if(pageno > dvi->npages-1) + pageno = dvi->npages - 1; + dvi->currpage = pageno; +} + +static int mdvi_run_macro(DviContext *dvi, Uchar *macro, size_t len) +{ + DviFontRef *curr, *fonts; + DviBuffer saved_buffer; + FILE *saved_file; + int opcode; + int oldtop; + + dvi->depth++; + push(dvi, DVI_PUSH); + dvi->pos.w = 0; + dvi->pos.x = 0; + dvi->pos.y = 0; + dvi->pos.z = 0; + + /* save our state */ + curr = dvi->currfont; + fonts = dvi->fonts; + saved_buffer = dvi->buffer; + saved_file = dvi->in; + dvi->currfont = curr->ref->subfonts; + dvi->fonts = curr->ref->subfonts; + dvi->buffer.data = macro; + dvi->buffer.pos = 0; + dvi->buffer.length = len; + dvi->buffer.frozen = 1; + dvi->in = NULL; + oldtop = dvi->stacktop; + + /* execute commands */ + while((opcode = duget1(dvi)) != DVI_EOP) { + if(dvi_commands[opcode](dvi, opcode) < 0) + break; + } + if(opcode != DVI_EOP) + dviwarn(dvi, _("%s: vf macro had errors\n"), + curr->ref->fontname); + if(dvi->stacktop != oldtop) + dviwarn(dvi, _("%s: stack not empty after vf macro\n"), + curr->ref->fontname); + + /* restore things */ + pop(dvi, DVI_POP); + dvi->currfont = curr; + dvi->fonts = fonts; + dvi->buffer = saved_buffer; + dvi->in = saved_file; + dvi->depth--; + + return (opcode != DVI_EOP ? -1 : 0); +} + +int mdvi_dopage(DviContext *dvi, int pageno) +{ + int op; + int ppi; + int reloaded = 0; + +again: + if(dvi->in == NULL) { + /* try reopening the file */ + dvi->in = fopen(dvi->filename, "rb"); + if(dvi->in == NULL) { + mdvi_warning(_("%s: could not reopen file (%s)\n"), + dvi->filename, + strerror(errno)); + return -1; + } + DEBUG((DBG_FILES, "reopen(%s) -> Ok\n", dvi->filename)); + } + + /* check if we need to reload the file */ + if(!reloaded && get_mtime(fileno(dvi->in)) > dvi->modtime) { + mdvi_reload(dvi, &dvi->params); + /* we have to reopen the file, again */ + reloaded = 1; + goto again; + } + + if(pageno < 0 || pageno > dvi->npages-1) { + mdvi_error(_("%s: page %d out of range\n"), + dvi->filename, pageno); + return -1; + } + + fseek(dvi->in, (long)dvi->pagemap[pageno][0], SEEK_SET); + if((op = fuget1(dvi->in)) != DVI_BOP) { + mdvi_error(_("%s: bad offset at page %d\n"), + dvi->filename, pageno+1); + return -1; + } + + /* skip bop */ + fseek(dvi->in, (long)44, SEEK_CUR); + + /* reset state */ + dvi->currfont = NULL; + memzero(&dvi->pos, sizeof(DviState)); + dvi->stacktop = 0; + dvi->currpage = pageno; + dvi->curr_layer = 0; + + if(dvi->buffer.data && !dvi->buffer.frozen) + mdvi_free(dvi->buffer.data); + + /* reset our buffer */ + dvi->buffer.data = NULL; + dvi->buffer.length = 0; + dvi->buffer.pos = 0; + dvi->buffer.frozen = 0; + +#if 0 /* make colors survive page breaks */ + /* reset color stack */ + mdvi_reset_color(dvi); +#endif + + /* set max horizontal and vertical drift (from dvips) */ + if(dvi->params.hdrift < 0) { + ppi = dvi->params.dpi / dvi->params.hshrink; /* shrunk pixels per inch */ + if(ppi < 600) + dvi->params.hdrift = ppi / 100; + else if(ppi < 1200) + dvi->params.hdrift = ppi / 200; + else + dvi->params.hdrift = ppi / 400; + } + if(dvi->params.vdrift < 0) { + ppi = dvi->params.vdpi / dvi->params.vshrink; /* shrunk pixels per inch */ + if(ppi < 600) + dvi->params.vdrift = ppi / 100; + else if(ppi < 1200) + dvi->params.vdrift = ppi / 200; + else + dvi->params.vdrift = ppi / 400; + } + + dvi->params.thinsp = FROUND(0.025 * dvi->params.dpi / dvi->params.conv); + dvi->params.vsmallsp = FROUND(0.025 * dvi->params.vdpi / dvi->params.vconv); + + /* execute all the commands in the page */ + while((op = duget1(dvi)) != DVI_EOP) { + if(dvi_commands[op](dvi, op) < 0) + break; + } + + fflush(stdout); + fflush(stderr); + if(op != DVI_EOP) + return -1; + if(dvi->stacktop) + dviwarn(dvi, _("stack not empty at end of page\n")); + return 0; +} + +static int inline move_vertical(DviContext *dvi, int amount) +{ + int rvv; + + dvi->pos.v += amount; + rvv = vpixel_round(dvi, dvi->pos.v); + if(!dvi->params.vdrift) + return rvv; + if(amount > dvi->params.vsmallsp || amount <= -dvi->params.vsmallsp) + return rvv; + else { + int newvv; + + newvv = dvi->pos.vv + vpixel_round(dvi, amount); + if(rvv - newvv > dvi->params.vdrift) + return rvv - dvi->params.vdrift; + else if(newvv - rvv > dvi->params.vdrift) + return rvv + dvi->params.vdrift; + else + return newvv; + } +} + +static int inline move_horizontal(DviContext *dvi, int amount) +{ + int rhh; + + dvi->pos.h += amount; + rhh = pixel_round(dvi, dvi->pos.h); + if(!dvi->params.hdrift) + return rhh; + else if(amount > dvi->params.thinsp || amount <= -6 * dvi->params.thinsp) + return rhh; + else { + int newhh; + + newhh = dvi->pos.hh + pixel_round(dvi, amount); + if(rhh - newhh > dvi->params.hdrift) + return rhh - dvi->params.hdrift; + else if(newhh - rhh > dvi->params.hdrift) + return rhh + dvi->params.hdrift; + else + return newhh; + } +} + +static void inline fix_after_horizontal(DviContext *dvi) +{ + int rhh; + + rhh = pixel_round(dvi, dvi->pos.h); + if(!dvi->params.hdrift) + dvi->pos.hh = rhh; + else if(rhh - dvi->pos.hh > dvi->params.hdrift) + dvi->pos.hh = rhh - dvi->params.hdrift; + else if(dvi->pos.hh - rhh > dvi->params.hdrift) + dvi->pos.hh = rhh + dvi->params.hdrift; +} + +/* commands */ + +#define DBGSUM(a,b,c) \ + (a), (b) > 0 ? '+' : '-', \ + (b) > 0 ? (b) : -(b), (c) + +/* + * Draw rules with some sort of antialias support. Usefult for high-rate + * scale factors. + */ + +static void draw_shrink_rule (DviContext *dvi, int x, int y, Uint w, Uint h, int f) +{ + int hs, vs, npixels; + Ulong fg, bg; + Ulong *pixels; + + hs = dvi->params.hshrink; + vs = dvi->params.vshrink; + fg = dvi->curr_fg; + bg = dvi->curr_bg; + + if (MDVI_ENABLED(dvi, MDVI_PARAM_ANTIALIASED)) { + npixels = vs * hs + 1; + pixels = get_color_table(&dvi->device, npixels, bg, fg, + dvi->params.gamma, dvi->params.density); + + if (pixels) { + int color; + + /* Lines with width 1 should be perfectly visible + * in shrink about 15. That is the reason of constant + */ + + color = (pow (vs / h * hs, 2) + pow (hs / w * vs, 2)) / 225; + if (color < npixels) { + fg = pixels[color]; + } else { + fg = pixels[npixels - 1]; + } + } + } + + mdvi_push_color (dvi, fg, bg); + dvi->device.draw_rule(dvi, x, y, w, h, f); + mdvi_pop_color (dvi); + + return; +} + +/* + * The only commands that actually draw something are: + * set_char, set_rule + */ + +static void draw_box(DviContext *dvi, DviFontChar *ch) +{ + DviGlyph *glyph = NULL; + int x, y, w, h; + + if(!MDVI_GLYPH_UNSET(ch->shrunk.data)) + glyph = &ch->shrunk; + else if(!MDVI_GLYPH_UNSET(ch->grey.data)) + glyph = &ch->grey; + else if(!MDVI_GLYPH_UNSET(ch->glyph.data)) + glyph = &ch->glyph; + if(glyph == NULL) + return; + x = glyph->x; + y = glyph->y; + w = glyph->w; + h = glyph->h; + /* this is bad -- we have to undo the orientation */ + switch(dvi->params.orientation) { + case MDVI_ORIENT_TBLR: + break; + case MDVI_ORIENT_TBRL: + x = w - x; + break; + case MDVI_ORIENT_BTLR: + y = h - y; + break; + case MDVI_ORIENT_BTRL: + x = w - x; + y = h - y; + break; + case MDVI_ORIENT_RP90: + SWAPINT(w, h); + SWAPINT(x, y); + x = w - x; + break; + case MDVI_ORIENT_RM90: + SWAPINT(w, h); + SWAPINT(x, y); + y = h - y; + break; + case MDVI_ORIENT_IRP90: + SWAPINT(w, h); + SWAPINT(x, y); + break; + case MDVI_ORIENT_IRM90: + SWAPINT(w, h); + SWAPINT(x, y); + x = w - x; + y = h - y; + break; + } + + draw_shrink_rule(dvi, dvi->pos.hh - x, dvi->pos.vv - y, w, h, 1); +} + +int set_char(DviContext *dvi, int opcode) +{ + int num; + int h; + int hh; + DviFontChar *ch; + DviFont *font; + + if(opcode < 128) + num = opcode; + else + num = dugetn(dvi, opcode - DVI_SET1 + 1); + if(dvi->currfont == NULL) { + dvierr(dvi, _("no default font set yet\n")); + return -1; + } + font = dvi->currfont->ref; + ch = font_get_glyph(dvi, font, num); + if(ch == NULL || ch->missing) { + /* try to display something anyway */ + ch = FONTCHAR(font, num); + if(!glyph_present(ch)) { + dviwarn(dvi, + _("requested character %d does not exist in `%s'\n"), + num, font->fontname); + return 0; + } + draw_box(dvi, ch); + } else if(dvi->curr_layer <= dvi->params.layer) { + if(ISVIRTUAL(font)) + mdvi_run_macro(dvi, (Uchar *)font->private + + ch->offset, ch->width); + else if(ch->width && ch->height) + dvi->device.draw_glyph(dvi, ch, + dvi->pos.hh, dvi->pos.vv); + } + if(opcode >= DVI_PUT1 && opcode <= DVI_PUT4) { + SHOWCMD((dvi, "putchar", opcode - DVI_PUT1 + 1, + "char %d (%s)\n", + num, dvi->currfont->ref->fontname)); + } else { + h = dvi->pos.h + ch->tfmwidth; + hh = dvi->pos.hh + pixel_round(dvi, ch->tfmwidth); + SHOWCMD((dvi, "setchar", num, "(%d,%d) h:=%d%c%d=%d, hh:=%d (%s)\n", + dvi->pos.hh, dvi->pos.vv, + DBGSUM(dvi->pos.h, ch->tfmwidth, h), hh, + font->fontname)); + dvi->pos.h = h; + dvi->pos.hh = hh; + fix_after_horizontal(dvi); + } + + return 0; +} + +int set_rule(DviContext *dvi, int opcode) +{ + Int32 a, b; + int h, w; + + a = dsget4(dvi); + b = dsget4(dvi); w = rule_round(dvi, b); + if(a > 0 && b > 0) { + h = vrule_round(dvi, a); + SHOWCMD((dvi, opcode == DVI_SET_RULE ? "setrule" : "putrule", -1, + "width %d, height %d (%dx%d pixels)\n", + b, a, w, h)); + /* the `draw' functions expect the origin to be at the top left + * corner of the rule, not the bottom left, as in DVI files */ + if(dvi->curr_layer <= dvi->params.layer) { + draw_shrink_rule(dvi, + dvi->pos.hh, dvi->pos.vv - h + 1, w, h, 1); + } + } else { + SHOWCMD((dvi, opcode == DVI_SET_RULE ? "setrule" : "putrule", -1, + "(moving left only, by %d)\n", b)); + } + + if(opcode == DVI_SET_RULE) { + dvi->pos.h += b; + dvi->pos.hh += w; + fix_after_horizontal(dvi); + } + return 0; +} + +int no_op(DviContext *dvi, int opcode) +{ + SHOWCMD((dvi, "noop", -1, "")); + return 0; +} + +int push(DviContext *dvi, int opcode) +{ + if(dvi->stacktop == dvi->stacksize) { + if(!dvi->depth) + dviwarn(dvi, _("enlarging stack\n")); + dvi->stacksize += 8; + dvi->stack = xresize(dvi->stack, + DviState, dvi->stacksize); + } + memcpy(&dvi->stack[dvi->stacktop], &dvi->pos, sizeof(DviState)); + SHOWCMD((dvi, "push", -1, + "level %d: (h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=%d,vv=%d)\n", + dvi->stacktop, + dvi->pos.h, dvi->pos.v, dvi->pos.w, dvi->pos.x, + dvi->pos.y, dvi->pos.z, dvi->pos.hh, dvi->pos.vv)); + dvi->stacktop++; + return 0; +} + +int pop(DviContext *dvi, int opcode) +{ + if(dvi->stacktop == 0) { + dvierr(dvi, _("stack underflow\n")); + return -1; + } + memcpy(&dvi->pos, &dvi->stack[dvi->stacktop-1], sizeof(DviState)); + SHOWCMD((dvi, "pop", -1, + "level %d: (h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=%d,vv=%d)\n", + dvi->stacktop, + dvi->pos.h, dvi->pos.v, dvi->pos.w, dvi->pos.x, + dvi->pos.y, dvi->pos.z, dvi->pos.hh, dvi->pos.vv)); + dvi->stacktop--; + return 0; +} + +int move_right(DviContext *dvi, int opcode) +{ + Int32 arg; + int h, hh; + + arg = dsgetn(dvi, opcode - DVI_RIGHT1 + 1); + h = dvi->pos.h; + hh = move_horizontal(dvi, arg); + SHOWCMD((dvi, "right", opcode - DVI_RIGHT1 + 1, + "%d h:=%d%c%d=%d, hh:=%d\n", + arg, DBGSUM(h, arg, dvi->pos.h), hh)); + dvi->pos.hh = hh; + return 0; +} + +int move_down(DviContext *dvi, int opcode) +{ + Int32 arg; + int v, vv; + + arg = dsgetn(dvi, opcode - DVI_DOWN1 + 1); + v = dvi->pos.v; + vv = move_vertical(dvi, arg); + SHOWCMD((dvi, "down", opcode - DVI_DOWN1 + 1, + "%d v:=%d%c%d=%d, vv:=%d\n", + arg, DBGSUM(v, arg, dvi->pos.v), vv)); + dvi->pos.vv = vv; + return 0; +} + +int move_w(DviContext *dvi, int opcode) +{ + int h, hh; + + if(opcode != DVI_W0) + dvi->pos.w = dsgetn(dvi, opcode - DVI_W0); + h = dvi->pos.h; + hh = move_horizontal(dvi, dvi->pos.w); + SHOWCMD((dvi, "w", opcode - DVI_W0, + "%d h:=%d%c%d=%d, hh:=%d\n", + dvi->pos.w, DBGSUM(h, dvi->pos.w, dvi->pos.h), hh)); + dvi->pos.hh = hh; + return 0; +} + +int move_x(DviContext *dvi, int opcode) +{ + int h, hh; + + if(opcode != DVI_X0) + dvi->pos.x = dsgetn(dvi, opcode - DVI_X0); + h = dvi->pos.h; + hh = move_horizontal(dvi, dvi->pos.x); + SHOWCMD((dvi, "x", opcode - DVI_X0, + "%d h:=%d%c%d=%d, hh:=%d\n", + dvi->pos.x, DBGSUM(h, dvi->pos.x, dvi->pos.h), hh)); + dvi->pos.hh = hh; + return 0; +} + +int move_y(DviContext *dvi, int opcode) +{ + int v, vv; + + if(opcode != DVI_Y0) + dvi->pos.y = dsgetn(dvi, opcode - DVI_Y0); + v = dvi->pos.v; + vv = move_vertical(dvi, dvi->pos.y); + SHOWCMD((dvi, "y", opcode - DVI_Y0, + "%d h:=%d%c%d=%d, hh:=%d\n", + dvi->pos.y, DBGSUM(v, dvi->pos.y, dvi->pos.v), vv)); + dvi->pos.vv = vv; + return 0; +} + +int move_z(DviContext *dvi, int opcode) +{ + int v, vv; + + if(opcode != DVI_Z0) + dvi->pos.z = dsgetn(dvi, opcode - DVI_Z0); + v = dvi->pos.v; + vv = move_vertical(dvi, dvi->pos.z); + SHOWCMD((dvi, "z", opcode - DVI_Z0, + "%d h:=%d%c%d=%d, hh:=%d\n", + dvi->pos.z, DBGSUM(v, dvi->pos.z, dvi->pos.v), vv)); + dvi->pos.vv = vv; + return 0; +} + +int sel_font(DviContext *dvi, int opcode) +{ + DviFontRef *ref; + int ndx; + + ndx = opcode - DVI_FNT_NUM0; + if(dvi->depth) + ref = font_find_flat(dvi, ndx); + else + ref = dvi->findref(dvi, ndx); + if(ref == NULL) { + dvierr(dvi, _("font %d is not defined\n"), + opcode - DVI_FNT_NUM0); + return -1; + } + SHOWCMD((dvi, "fntnum", opcode - DVI_FNT_NUM0, + "current font is %s\n", + ref->ref->fontname)); + dvi->currfont = ref; + return 0; +} + +int sel_fontn(DviContext *dvi, int opcode) +{ + Int32 arg; + DviFontRef *ref; + + arg = dugetn(dvi, opcode - DVI_FNT1 + 1); + if(dvi->depth) + ref = font_find_flat(dvi, arg); + else + ref = dvi->findref(dvi, arg); + if(ref == NULL) { + dvierr(dvi, _("font %d is not defined\n"), arg); + return -1; + } + SHOWCMD((dvi, "fnt", opcode - DVI_FNT1 + 1, + "current font is %s (id %d)\n", + ref->ref->fontname, arg)); + dvi->currfont = ref; + return 0; +} + +int special(DviContext *dvi, int opcode) +{ + char *s; + Int32 arg; + + arg = dugetn(dvi, opcode - DVI_XXX1 + 1); + s = mdvi_malloc(arg + 1); + dread(dvi, s, arg); + s[arg] = 0; + mdvi_do_special(dvi, s); + SHOWCMD((dvi, "XXXX", opcode - DVI_XXX1 + 1, + "[%s]", s)); + mdvi_free(s); + return 0; +} + +int def_font(DviContext *dvi, int opcode) +{ + DviFontRef *ref; + Int32 arg; + + arg = dugetn(dvi, opcode - DVI_FNT_DEF1 + 1); + if(dvi->depth) + ref = font_find_flat(dvi, arg); + else + ref = dvi->findref(dvi, arg); + /* skip the rest */ + dskip(dvi, 12); + dskip(dvi, duget1(dvi) + duget1(dvi)); + if(ref == NULL) { + dvierr(dvi, _("font %d is not defined in postamble\n"), arg); + return -1; + } + SHOWCMD((dvi, "fntdef", opcode - DVI_FNT_DEF1 + 1, + "%d -> %s (%d links)\n", + ref->fontid, ref->ref->fontname, + ref->ref->links)); + return 0; +} + +int unexpected(DviContext *dvi, int opcode) +{ + dvierr(dvi, _("unexpected opcode %d\n"), opcode); + return -1; +} + +int undefined(DviContext *dvi, int opcode) +{ + dvierr(dvi, _("undefined opcode %d\n"), opcode); + return -1; +} + diff --git a/backend/dvi/mdvi-lib/files.c b/backend/dvi/mdvi-lib/files.c new file mode 100644 index 00000000..b7065068 --- /dev/null +++ b/backend/dvi/mdvi-lib/files.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include "common.h" + +char *dgets(Dstring *dstr, FILE *in) +{ + char buffer[256]; + + dstr->length = 0; + if(feof(in)) + return NULL; + while(fgets(buffer, 256, in) != NULL) { + int len = strlen(buffer); + + if(buffer[len-1] == '\n') { + dstring_append(dstr, buffer, len - 1); + break; + } + dstring_append(dstr, buffer, len); + } + if(dstr->data) + dstr->data[dstr->length] = 0; + return dstr->data; +} + +/* some simple helper functions to manipulate file names */ + +const char *file_basename(const char *filename) +{ + const char *ptr = strrchr(filename, '/'); + + return (ptr ? ptr + 1 : filename); +} + +const char *file_extension(const char *filename) +{ + const char *ptr = strchr(file_basename(filename), '.'); + + return (ptr ? ptr + 1 : NULL); +} + +int file_readable(const char *filename) +{ + int status = (access(filename, R_OK) == 0); + + DEBUG((DBG_FILES, "file_redable(%s) -> %s\n", + filename, status ? "Yes" : "No")); + return status; +} + +int file_exists(const char *filename) +{ + int status = (access(filename, F_OK) == 0); + + DEBUG((DBG_FILES, "file_exists(%s) -> %s\n", + filename, status ? "Yes" : "No")); + return status; +} + diff --git a/backend/dvi/mdvi-lib/font.c b/backend/dvi/mdvi-lib/font.c new file mode 100644 index 00000000..2f655df0 --- /dev/null +++ b/backend/dvi/mdvi-lib/font.c @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include "mdvi.h" +#include "private.h" + +static ListHead fontlist; + +extern char *_mdvi_fallback_font; + +extern void vf_free_macros(DviFont *); + +#define finfo search.info +#define TYPENAME(font) \ + ((font)->finfo ? (font)->finfo->name : "none") + +int font_reopen(DviFont *font) +{ + if(font->in) + fseek(font->in, (long)0, SEEK_SET); + else if((font->in = fopen(font->filename, "rb")) == NULL) { + DEBUG((DBG_FILES, "reopen(%s) -> Error\n", font->filename)); + return -1; + } + DEBUG((DBG_FILES, "reopen(%s) -> Ok.\n", font->filename)); + return 0; +} + +/* used from context: params and device */ +static int load_font_file(DviParams *params, DviFont *font) +{ + int status; + + if(SEARCH_DONE(font->search)) + return -1; + if(font->in == NULL && font_reopen(font) < 0) + return -1; + DEBUG((DBG_FONTS, "%s: loading %s font from `%s'\n", + font->fontname, + font->finfo->name, font->filename)); + do { + status = font->finfo->load(params, font); + } while(status < 0 && mdvi_font_retry(params, font) == 0); + if(status < 0) + return -1; + if(font->in) { + fclose(font->in); + font->in = NULL; + } + DEBUG((DBG_FONTS, "reload_font(%s) -> %s\n", + font->fontname, status < 0 ? "Error" : "Ok")); + return 0; +} + +void font_drop_one(DviFontRef *ref) +{ + DviFont *font; + + font = ref->ref; + mdvi_free(ref); + /* drop all children */ + for(ref = font->subfonts; ref; ref = ref->next) { + /* just adjust the reference counts */ + ref->ref->links--; + } + if(--font->links == 0) { + /* + * this font doesn't have any more references, but + * we still keep it around in case a virtual font + * requests it. + */ + if(font->in) { + fclose(font->in); + font->in = NULL; + } + if(LIST(font) != fontlist.tail) { + /* move it to the end of the list */ + listh_remove(&fontlist, LIST(font)); + listh_append(&fontlist, LIST(font)); + } + } + DEBUG((DBG_FONTS, "%s: reference dropped, %d more left\n", + font->fontname, font->links)); +} + +void font_drop_chain(DviFontRef *head) +{ + DviFontRef *ptr; + + for(; (ptr = head); ) { + head = ptr->next; + font_drop_one(ptr); + } +} + +int font_free_unused(DviDevice *dev) +{ + DviFont *font, *next; + int count = 0; + + DEBUG((DBG_FONTS, "destroying unused fonts\n")); + for(font = (DviFont *)fontlist.head; font; font = next) { + DviFontRef *ref; + + next = font->next; + if(font->links) + continue; + count++; + DEBUG((DBG_FONTS, "removing unused %s font `%s'\n", + TYPENAME(font), font->fontname)); + listh_remove(&fontlist, LIST(font)); + if(font->in) + fclose(font->in); + /* get rid of subfonts (but can't use `drop_chain' here) */ + for(; (ref = font->subfonts); ) { + font->subfonts = ref->next; + mdvi_free(ref); + } + /* remove this font */ + font_reset_font_glyphs(dev, font, MDVI_FONTSEL_GLYPH); + /* let the font destroy its private data */ + if(font->finfo->freedata) + font->finfo->freedata(font); + /* destroy characters */ + if(font->chars) + mdvi_free(font->chars); + mdvi_free(font->fontname); + mdvi_free(font->filename); + mdvi_free(font); + } + DEBUG((DBG_FONTS, "%d unused fonts removed\n", count)); + return count; +} + +/* used from context: params and device */ +DviFontRef * +font_reference( + DviParams *params, /* rendering parameters */ + Int32 id, /* external id number */ + const char *name, /* font name */ + Int32 sum, /* checksum (from DVI of VF) */ + int hdpi, /* resolution */ + int vdpi, + Int32 scale) /* scaling factor (from DVI or VF) */ +{ + DviFont *font; + DviFontRef *ref; + DviFontRef *subfont_ref; + + /* see if there is a font with the same characteristics */ + for(font = (DviFont *)fontlist.head; font; font = font->next) { + if(strcmp(name, font->fontname) == 0 + && (!sum || !font->checksum || font->checksum == sum) + && font->hdpi == hdpi + && font->vdpi == vdpi + && font->scale == scale) + break; + } + /* try to load the font */ + if(font == NULL) { + font = mdvi_add_font(name, sum, hdpi, vdpi, scale); + if(font == NULL) + return NULL; + listh_append(&fontlist, LIST(font)); + } + if(!font->links && !font->chars && load_font_file(params, font) < 0) { + DEBUG((DBG_FONTS, "font_reference(%s) -> Error\n", name)); + return NULL; + } + ref = xalloc(DviFontRef); + ref->ref = font; + + font->links++; + for(subfont_ref = font->subfonts; subfont_ref; subfont_ref = subfont_ref->next) { + /* just adjust the reference counts */ + subfont_ref->ref->links++; + } + + ref->fontid = id; + + if(LIST(font) != fontlist.head) { + listh_remove(&fontlist, LIST(font)); + listh_prepend(&fontlist, LIST(font)); + } + + DEBUG((DBG_FONTS, "font_reference(%s) -> %d links\n", + font->fontname, font->links)); + return ref; +} + +void font_transform_glyph(DviOrientation orient, DviGlyph *g) +{ + BITMAP *map; + int x, y; + + map = (BITMAP *)g->data; + if(MDVI_GLYPH_ISEMPTY(map)) + map = NULL; + + /* put the glyph in the right orientation */ + switch(orient) { + case MDVI_ORIENT_TBLR: + break; + case MDVI_ORIENT_TBRL: + g->x = g->w - g->x; + if(map) bitmap_flip_horizontally(map); + break; + case MDVI_ORIENT_BTLR: + g->y = g->h - g->y; + if(map) bitmap_flip_vertically(map); + break; + case MDVI_ORIENT_BTRL: + g->x = g->w - g->x; + g->y = g->h - g->y; + if(map) bitmap_flip_diagonally(map); + break; + case MDVI_ORIENT_RP90: + if(map) bitmap_rotate_counter_clockwise(map); + y = g->y; + x = g->w - g->x; + g->x = y; + g->y = x; + SWAPINT(g->w, g->h); + break; + case MDVI_ORIENT_RM90: + if(map) bitmap_rotate_clockwise(map); + y = g->h - g->y; + x = g->x; + g->x = y; + g->y = x; + SWAPINT(g->w, g->h); + break; + case MDVI_ORIENT_IRP90: + if(map) bitmap_flip_rotate_counter_clockwise(map); + y = g->y; + x = g->x; + g->x = y; + g->y = x; + SWAPINT(g->w, g->h); + break; + case MDVI_ORIENT_IRM90: + if(map) bitmap_flip_rotate_clockwise(map); + y = g->h - g->y; + x = g->w - g->x; + g->x = y; + g->y = x; + SWAPINT(g->w, g->h); + break; + } +} + +static int load_one_glyph(DviContext *dvi, DviFont *font, int code) +{ + BITMAP *map; + DviFontChar *ch; + int status; + +#ifndef NODEBUG + ch = FONTCHAR(font, code); + DEBUG((DBG_GLYPHS, "loading glyph code %d in %s (at %u)\n", + code, font->fontname, ch->offset)); +#endif + if(font->finfo->getglyph == NULL) { + /* font type does not need to load glyphs (e.g. vf) */ + return 0; + } + + status = font->finfo->getglyph(&dvi->params, font, code); + if(status < 0) + return -1; + /* get the glyph again (font->chars may have changed) */ + ch = FONTCHAR(font, code); +#ifndef NODEBUG + map = (BITMAP *)ch->glyph.data; + if(DEBUGGING(BITMAP_DATA)) { + DEBUG((DBG_BITMAP_DATA, + "%s: new %s bitmap for character %d:\n", + font->fontname, TYPENAME(font), code)); + if(MDVI_GLYPH_ISEMPTY(map)) + DEBUG((DBG_BITMAP_DATA, "blank bitmap\n")); + else + bitmap_print(stderr, map); + } +#endif + /* check if we have to scale it */ + if(!font->finfo->scalable && font->hdpi != font->vdpi) { + int hs, vs, d; + + /* we scale it ourselves */ + d = Max(font->hdpi, font->vdpi); + hs = d / font->hdpi; + vs = d / font->vdpi; + if(ch->width && ch->height && (hs > 1 || vs > 1)) { + int h, v; + DviGlyph glyph; + + DEBUG((DBG_FONTS, + "%s: scaling glyph %d to resolution %dx%d\n", + font->fontname, code, font->hdpi, font->vdpi)); + h = dvi->params.hshrink; + v = dvi->params.vshrink; + d = dvi->params.density; + dvi->params.hshrink = hs; + dvi->params.vshrink = vs; + dvi->params.density = 50; + /* shrink it */ + font->finfo->shrink0(dvi, font, ch, &glyph); + /* restore parameters */ + dvi->params.hshrink = h; + dvi->params.vshrink = v; + dvi->params.density = d; + /* update glyph data */ + if(!MDVI_GLYPH_ISEMPTY(ch->glyph.data)) + bitmap_destroy((BITMAP *)ch->glyph.data); + ch->glyph.data = glyph.data; + ch->glyph.x = glyph.x; + ch->glyph.y = glyph.y; + ch->glyph.w = glyph.w; + ch->glyph.h = glyph.h; + } + + } + font_transform_glyph(dvi->params.orientation, &ch->glyph); + + return 0; +} + +DviFontChar *font_get_glyph(DviContext *dvi, DviFont *font, int code) +{ + DviFontChar *ch; + +again: + /* if we have not loaded the font yet, do so now */ + if(!font->chars && load_font_file(&dvi->params, font) < 0) + return NULL; + + /* get the unscaled glyph, maybe loading it from disk */ + ch = FONTCHAR(font, code); + if(!ch || !glyph_present(ch)) + return NULL; + if(!ch->loaded && load_one_glyph(dvi, font, code) == -1) { + if(font->chars == NULL) { + /* we need to try another font class */ + goto again; + } + return NULL; + } + /* yes, we have to do this again */ + ch = FONTCHAR(font, code); + + /* Got the glyph. If we also have the right scaled glyph, do no more */ + if(!ch->width || !ch->height || + font->finfo->getglyph == NULL || + (dvi->params.hshrink == 1 && dvi->params.vshrink == 1)) + return ch; + + /* If the glyph is empty, we just need to shrink the box */ + if(ch->missing || MDVI_GLYPH_ISEMPTY(ch->glyph.data)) { + if(MDVI_GLYPH_UNSET(ch->shrunk.data)) + mdvi_shrink_box(dvi, font, ch, &ch->shrunk); + return ch; + } else if(MDVI_ENABLED(dvi, MDVI_PARAM_ANTIALIASED)) { + if(ch->grey.data && + !MDVI_GLYPH_ISEMPTY(ch->grey.data) && + ch->fg == dvi->curr_fg && + ch->bg == dvi->curr_bg) + return ch; + if(ch->grey.data && + !MDVI_GLYPH_ISEMPTY(ch->grey.data)) { + if(dvi->device.free_image) + dvi->device.free_image(ch->grey.data); + ch->grey.data = NULL; + } + font->finfo->shrink1(dvi, font, ch, &ch->grey); + } else if(!ch->shrunk.data) + font->finfo->shrink0(dvi, font, ch, &ch->shrunk); + + return ch; +} + +void font_reset_one_glyph(DviDevice *dev, DviFontChar *ch, int what) +{ + if(!glyph_present(ch)) + return; + if(what & MDVI_FONTSEL_BITMAP) { + if(MDVI_GLYPH_NONEMPTY(ch->shrunk.data)) + bitmap_destroy((BITMAP *)ch->shrunk.data); + ch->shrunk.data = NULL; + } + if(what & MDVI_FONTSEL_GREY) { + if(MDVI_GLYPH_NONEMPTY(ch->grey.data)) { + if(dev->free_image) + dev->free_image(ch->grey.data); + } + ch->grey.data = NULL; + } + if(what & MDVI_FONTSEL_GLYPH) { + if(MDVI_GLYPH_NONEMPTY(ch->glyph.data)) + bitmap_destroy((BITMAP *)ch->glyph.data); + ch->glyph.data = NULL; + ch->loaded = 0; + } +} + +void font_reset_font_glyphs(DviDevice *dev, DviFont *font, int what) +{ + int i; + DviFontChar *ch; + + if(what & MDVI_FONTSEL_GLYPH) + what |= MDVI_FONTSEL_BITMAP|MDVI_FONTSEL_GREY; + if(font->subfonts) { + DviFontRef *ref; + + for(ref = font->subfonts; ref; ref = ref->next) + font_reset_font_glyphs(dev, ref->ref, what); + } + if(font->in) { + DEBUG((DBG_FILES, "close(%s)\n", font->filename)); + fclose(font->in); + font->in = NULL; + } + if(font->finfo->getglyph == NULL) + return; + DEBUG((DBG_FONTS, "resetting glyphs in font `%s'\n", font->fontname)); + for(ch = font->chars, i = font->loc; i <= font->hic; ch++, i++) { + if(glyph_present(ch)) + font_reset_one_glyph(dev, ch, what); + } + if((what & MDVI_FONTSEL_GLYPH) && font->finfo->reset) + font->finfo->reset(font); +} + +void font_reset_chain_glyphs(DviDevice *dev, DviFontRef *head, int what) +{ + DviFontRef *ref; + + for(ref = head; ref; ref = ref->next) + font_reset_font_glyphs(dev, ref->ref, what); +} + +static int compare_refs(const void *p1, const void *p2) +{ + return ((*(DviFontRef **)p1)->fontid - (*(DviFontRef **)p2)->fontid); +} + +void font_finish_definitions(DviContext *dvi) +{ + int count; + DviFontRef **map, *ref; + + /* first get rid of unused fonts */ + font_free_unused(&dvi->device); + + if(dvi->fonts == NULL) { + mdvi_warning(_("%s: no fonts defined\n"), dvi->filename); + return; + } + map = xnalloc(DviFontRef *, dvi->nfonts); + for(count = 0, ref = dvi->fonts; ref; ref = ref->next) + map[count++] = ref; + /* sort the array by font id */ + qsort(map, dvi->nfonts, sizeof(DviFontRef *), compare_refs); + dvi->fontmap = map; +} + +DviFontRef *font_find_flat(DviContext *dvi, Int32 id) +{ + DviFontRef *ref; + + for(ref = dvi->fonts; ref; ref = ref->next) + if(ref->fontid == id) + break; + return ref; +} + +DviFontRef *font_find_mapped(DviContext *dvi, Int32 id) +{ + int lo, hi, n; + DviFontRef **map; + + /* do a binary search */ + lo = 0; hi = dvi->nfonts; + map = dvi->fontmap; + while(lo < hi) { + int sign; + + n = (hi + lo) >> 1; + sign = (map[n]->fontid - id); + if(sign == 0) + break; + else if(sign < 0) + lo = n; + else + hi = n; + } + if(lo >= hi) + return NULL; + return map[n]; +} + diff --git a/backend/dvi/mdvi-lib/fontmap.c b/backend/dvi/mdvi-lib/fontmap.c new file mode 100644 index 00000000..c3c3a8d3 --- /dev/null +++ b/backend/dvi/mdvi-lib/fontmap.c @@ -0,0 +1,1174 @@ +/* encoding.c - functions to manipulate encodings and fontmaps */ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "mdvi.h" +#include "private.h" + +#include +#include + +typedef struct _DviFontMap DviFontMap; + +struct _DviFontMap { + ListHead entries; + DviHashTable fonts; +}; + +typedef struct _PSFontMap { + struct _PSFontMap *next; + struct _PSFontMap *prev; + char *psname; + char *mapname; + char *fullname; +} PSFontMap; + +/* these variables control PS font maps */ +static char *pslibdir = NULL; /* path where we look for PS font maps */ +static char *psfontdir = NULL; /* PS font search path */ +static int psinitialized = 0; /* did we expand the path already? */ + +static ListHead psfonts = MDVI_EMPTY_LIST_HEAD; +static DviHashTable pstable = MDVI_EMPTY_HASH_TABLE; + +static ListHead fontmaps; +static DviHashTable maptable; +static int fontmaps_loaded = 0; + +#define MAP_HASH_SIZE 57 +#define ENC_HASH_SIZE 31 +#define PSMAP_HASH_SIZE 57 + +/* this hash table should be big enough to + * hold (ideally) one glyph name per bucket */ +#define ENCNAME_HASH_SIZE 131 /* most TeX fonts have 128 glyphs */ + +static ListHead encodings = MDVI_EMPTY_LIST_HEAD; +static DviEncoding *tex_text_encoding = NULL; +static DviEncoding *default_encoding = NULL; + +/* we keep two hash tables for encodings: one for their base files (e.g. + * "8r.enc"), and another one for their names (e.g. "TeXBase1Encoding") */ +static DviHashTable enctable = MDVI_EMPTY_HASH_TABLE; +static DviHashTable enctable_file = MDVI_EMPTY_HASH_TABLE; + +/* the TeX text encoding, from dvips */ +static char *tex_text_vector[256] = { + "Gamma", "Delta", "Theta", "Lambda", "Xi", "Pi", "Sigma", "Upsilon", + "Phi", "Psi", "Omega", "arrowup", "arrowdown", "quotesingle", + "exclamdown", "questiondown", "dotlessi", "dotlessj", "grave", + "acute", "caron", "breve", "macron", "ring", "cedilla", + "germandbls", "ae", "oe", "oslash", "AE", "OE", "Oslash", "space", + "exclam", "quotedbl", "numbersign", "dollar", "percent", + "ampersand", "quoteright", "parenleft", "parenright", "asterisk", + "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", + "three", "four", "five", "six", "seven", "eight", "nine", "colon", + "semicolon", "less", "equal", "greater", "question", "at", "A", "B", + "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "bracketleft", "backslash", "bracketright", "circumflex", + "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", + "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", + "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "tilde", + "dieresis", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static void ps_init_default_paths __PROTO((void)); +static int mdvi_set_default_encoding __PROTO((const char *name)); +static int mdvi_init_fontmaps __PROTO((void)); + +/* + * What we do here is allocate one block large enough to hold the entire + * file (these files are small) minus the leading comments. This is much + * better than allocating up to 256 tiny strings per encoding vector. */ +static int read_encoding(DviEncoding *enc) +{ + FILE *in; + int curr; + char *line; + char *name; + char *next; + struct stat st; + + ASSERT(enc->private == NULL); + + in = fopen(enc->filename, "rb"); + if(in == NULL) { + DEBUG((DBG_FMAP, "%s: could not read `%s' (%s)\n", + enc->name, enc->filename, strerror(errno))); + return -1; + } + if(fstat(fileno(in), &st) < 0) { + /* should not happen */ + fclose(in); + return -1; + } + st.st_size -= enc->offset; + + /* this will be one big string */ + enc->private = (char *)malloc(st.st_size + 1); + /* setup the hash table */ + mdvi_hash_create(&enc->nametab, ENCNAME_HASH_SIZE); + /* setup the encoding vector */ + enc->vector = (char **)mdvi_malloc(256 * sizeof(char *)); + + /* jump to the beginning of the interesting part */ + fseek(in, enc->offset, SEEK_SET); + /* and read everything */ + if(fread(enc->private, st.st_size, 1, in) != 1) { + fclose(in); + mdvi_free(enc->private); + enc->private = NULL; + return -1; + } + /* we don't need this anymore */ + fclose(in); + curr = 0; + + next = name = NULL; + DEBUG((DBG_FMAP, "%s: reading encoding vector\n", enc->name)); + for(line = enc->private; *line && curr < 256; line = next) { + SKIPSP(line); + if(*line == ']') { + line++; SKIPSP(line); + if(STRNEQ(line, "def", 3)) + break; + } + name = getword(line, " \t\n", &next); + if(name == NULL) + break; + /* next > line */ + if(*name < ' ') + continue; + if(*name == '%') { + while(*next && *next != '\n') + next++; + if(*next) next++; /* skip \n */ + continue; + } + + /* got a name */ + if(*next) *next++ = 0; + + if(*name == '/') + name++; + enc->vector[curr] = name; + /* add it to the hash table */ + if(!STREQ(name, ".notdef")) { + mdvi_hash_add(&enc->nametab, MDVI_KEY(name), + Int2Ptr(curr + 1), MDVI_HASH_REPLACE); + } + curr++; + } + if(curr == 0) { + mdvi_hash_reset(&enc->nametab, 0); + mdvi_free(enc->private); + mdvi_free(enc); + return -1; + } + while(curr < 256) + enc->vector[curr++] = NULL; + return 0; +} + +static DviEncoding *find_encoding(const char *name) +{ + return (DviEncoding *)(encodings.count ? + mdvi_hash_lookup(&enctable, MDVI_KEY(name)) : NULL); +} + +static void destroy_encoding(DviEncoding *enc) +{ + if(enc == default_encoding) { + default_encoding = tex_text_encoding; + /* now we use reference counts again */ + mdvi_release_encoding(enc, 1); + } + if(enc != tex_text_encoding) { + mdvi_hash_reset(&enc->nametab, 0); + if(enc->private) { + mdvi_free(enc->private); + mdvi_free(enc->vector); + } + if(enc->name) + mdvi_free(enc->name); + if(enc->filename) + mdvi_free(enc->filename); + mdvi_free(enc); + } +} + +/* this is used for the `enctable_file' hash table */ +static void file_hash_free(DviHashKey key, void *data) +{ + mdvi_free(key); +} + +static DviEncoding *register_encoding(const char *basefile, int replace) +{ + DviEncoding *enc; + FILE *in; + char *filename; + char *name; + Dstring input; + char *line; + long offset; + + DEBUG((DBG_FMAP, "register_encoding(%s)\n", basefile)); + + if(encodings.count) { + enc = mdvi_hash_lookup(&enctable_file, MDVI_KEY(basefile)); + if(enc != NULL) { + DEBUG((DBG_FMAP, "%s: already there\n", basefile)); + return enc; /* no error */ + } + } + + /* try our own files first */ + filename = kpse_find_file(basefile, + kpse_program_text_format, 0); + + /* then try the system-wide ones */ + if(filename == NULL) + filename = kpse_find_file(basefile, + kpse_tex_ps_header_format, 0); + if(filename == NULL) + filename = kpse_find_file(basefile, + kpse_dvips_config_format, 0); + + /* finally try the given name */ + if(filename == NULL) + filename = mdvi_strdup(basefile); + + in = fopen(filename, "rb"); + if(in == NULL) { + mdvi_free(filename); + return NULL; + } + + /* just lookup the name of the encoding */ + name = NULL; + dstring_init(&input); + while((line = dgets(&input, in)) != NULL) { + if(STRNEQ(line, "Encoding=", 9)) { + name = getword(line + 9, " \t", &line); + if(*line) *line++ = 0; + break; + } else if(*line == '/') { + char *label = getword(line + 1, " \t", &line); + if(*line) { + *line++ = 0; + SKIPSP(line); + if(*line == '[') { + *line = 0; + name = label; + break; + } + } + } + } + offset = ftell(in); + fclose(in); + if(name == NULL || *name == 0) { + DEBUG((DBG_FMAP, + "%s: could not determine name of encoding\n", + basefile)); + mdvi_free(filename); + return NULL; + } + + /* check if the encoding is already there */ + enc = find_encoding(name); + if(enc == tex_text_encoding) { + /* A special case: if the vector we found is the static one, + * allow the user to override it with an external file */ + listh_remove(&encodings, LIST(enc)); + mdvi_hash_remove(&enctable, MDVI_KEY(enc->name)); + if(enc == default_encoding) + default_encoding = NULL; + } else if(enc) { + /* if the encoding is being used, refuse to remove it */ + if(enc->links) { + mdvi_free(filename); + dstring_reset(&input); + return NULL; + } + if(replace) { + mdvi_hash_remove(&enctable, MDVI_KEY(name)); + mdvi_hash_remove(&enctable_file, MDVI_KEY(basefile)); + listh_remove(&encodings, LIST(enc)); + if(enc == default_encoding) { + default_encoding = NULL; + mdvi_release_encoding(enc, 1); + } + DEBUG((DBG_FMAP, "%s: overriding encoding\n", name)); + destroy_encoding(enc); + } else { + mdvi_free(filename); + dstring_reset(&input); + return enc; /* no error */ + } + } + enc = xalloc(DviEncoding); + enc->name = mdvi_strdup(name); + enc->filename = filename; + enc->links = 0; + enc->offset = offset; + enc->private = NULL; + enc->vector = NULL; + mdvi_hash_init(&enc->nametab); + dstring_reset(&input); + if(default_encoding == NULL) + default_encoding = enc; + mdvi_hash_add(&enctable, MDVI_KEY(enc->name), + enc, MDVI_HASH_UNCHECKED); + mdvi_hash_add(&enctable_file, MDVI_KEY(mdvi_strdup(basefile)), + enc, MDVI_HASH_REPLACE); + listh_prepend(&encodings, LIST(enc)); + DEBUG((DBG_FMAP, "%s: encoding `%s' registered\n", + basefile, enc->name)); + return enc; +} + +DviEncoding *mdvi_request_encoding(const char *name) +{ + DviEncoding *enc = find_encoding(name); + + if(enc == NULL) { + DEBUG((DBG_FMAP, "%s: encoding not found, returning default `%s'\n", + name, default_encoding->name)); + return default_encoding; + } + /* we don't keep reference counts for this */ + if(enc == tex_text_encoding) + return enc; + if(!enc->private && read_encoding(enc) < 0) + return NULL; + enc->links++; + + /* if the hash table is empty, rebuild it */ + if(enc->nametab.nkeys == 0) { + int i; + + DEBUG((DBG_FMAP, "%s: rehashing\n", enc->name)); + for(i = 0; i < 256; i++) { + if(enc->vector[i] == NULL) + continue; + mdvi_hash_add(&enc->nametab, + MDVI_KEY(enc->vector[i]), + (DviHashKey)Int2Ptr(i), + MDVI_HASH_REPLACE); + } + } + return enc; +} + +void mdvi_release_encoding(DviEncoding *enc, int should_free) +{ + /* ignore our static encoding */ + if(enc == tex_text_encoding) + return; + if(!enc->links || --enc->links > 0 || !should_free) + return; + DEBUG((DBG_FMAP, "%s: resetting encoding vector\n", enc->name)); + mdvi_hash_reset(&enc->nametab, 1); /* we'll reuse it */ +} + +int mdvi_encode_glyph(DviEncoding *enc, const char *name) +{ + void *data; + + data = mdvi_hash_lookup(&enc->nametab, MDVI_KEY(name)); + if(data == NULL) + return -1; + /* we added +1 to the hashed index just to distinguish + * a failed lookup from a zero index. Adjust it now. */ + return (Ptr2Int(data) - 1); +} + +/**************** + * Fontmaps * + ****************/ + +static void parse_spec(DviFontMapEnt *ent, char *spec) +{ + char *arg, *command; + + /* this is a ridiculously simple parser, and recognizes only + * things of the form . Of these, only + * command=SlantFont, ExtendFont and ReEncodeFont are handled */ + while(*spec) { + arg = getword(spec, " \t", &spec); + if(*spec) *spec++ = 0; + command = getword(spec, " \t", &spec); + if(*spec) *spec++ = 0; + if(!arg || !command) + continue; + if(STREQ(command, "SlantFont")) { + double x = 10000 * strtod(arg, 0); + + /* SFROUND evaluates arguments twice */ + ent->slant = SFROUND(x); + } else if(STREQ(command, "ExtendFont")) { + double x = 10000 * strtod(arg, 0); + + ent->extend = SFROUND(x); + } else if(STREQ(command, "ReEncodeFont")) { + if(ent->encoding) + mdvi_free(ent->encoding); + ent->encoding = mdvi_strdup(arg); + } + } +} + +#if 0 +static void print_ent(DviFontMapEnt *ent) +{ + printf("Entry for `%s':\n", ent->fontname); + printf(" PS name: %s\n", ent->psname ? ent->psname : "(none)"); + printf(" Encoding: %s\n", ent->encoding ? ent->encoding : "(default)"); + printf(" EncFile: %s\n", ent->encfile ? ent->encfile : "(none)"); + printf(" FontFile: %s\n", ent->fontfile ? ent->fontfile : "(same)"); + printf(" Extend: %ld\n", ent->extend); + printf(" Slant: %ld\n", ent->slant); +} +#endif + +DviFontMapEnt *mdvi_load_fontmap(const char *file) +{ + char *ptr; + FILE *in; + int lineno = 1; + Dstring input; + ListHead list; + DviFontMapEnt *ent; + DviEncoding *last_encoding; + char *last_encfile; + + ptr = kpse_find_file(file, kpse_program_text_format, 0); + if(ptr == NULL) + ptr = kpse_find_file(file, kpse_tex_ps_header_format, 0); + if(ptr == NULL) + ptr = kpse_find_file(file, kpse_dvips_config_format, 0); + if(ptr == NULL) + in = fopen(file, "rb"); + else { + in = fopen(ptr, "rb"); + mdvi_free(ptr); + } + if(in == NULL) + return NULL; + + ent = NULL; + listh_init(&list); + dstring_init(&input); + last_encoding = NULL; + last_encfile = NULL; + + while((ptr = dgets(&input, in)) != NULL) { + char *font_file; + char *tex_name; + char *ps_name; + char *vec_name; + int is_encoding; + DviEncoding *enc; + + lineno++; + SKIPSP(ptr); + + /* we skip what dvips does */ + if(*ptr <= ' ' || *ptr == '*' || *ptr == '#' || + *ptr == ';' || *ptr == '%') + continue; + + font_file = NULL; + tex_name = NULL; + ps_name = NULL; + vec_name = NULL; + is_encoding = 0; + + if(ent == NULL) { + ent = xalloc(DviFontMapEnt); + ent->encoding = NULL; + ent->slant = 0; + ent->extend = 0; + } + while(*ptr) { + char *hdr_name = NULL; + + while(*ptr && *ptr <= ' ') + ptr++; + if(*ptr == 0) + break; + if(*ptr == '"') { + char *str; + + str = getstring(ptr, " \t", &ptr); + if(*ptr) *ptr++ = 0; + parse_spec(ent, str); + continue; + } else if(*ptr == '<') { + ptr++; + if(*ptr == '<') + ptr++; + else if(*ptr == '[') { + is_encoding = 1; + ptr++; + } + SKIPSP(ptr); + hdr_name = ptr; + } else if(!tex_name) + tex_name = ptr; + else if(!ps_name) + ps_name = ptr; + else + hdr_name = ptr; + + /* get next word */ + getword(ptr, " \t", &ptr); + if(*ptr) *ptr++ = 0; + + if(hdr_name) { + const char *ext = file_extension(hdr_name); + + if(is_encoding || (ext && STRCEQ(ext, "enc"))) + vec_name = hdr_name; + else + font_file = hdr_name; + } + } + + if(tex_name == NULL) + continue; + ent->fontname = mdvi_strdup(tex_name); + ent->psname = ps_name ? mdvi_strdup(ps_name) : NULL; + ent->fontfile = font_file ? mdvi_strdup(font_file) : NULL; + ent->encfile = vec_name ? mdvi_strdup(vec_name) : NULL; + ent->fullfile = NULL; + enc = NULL; /* we don't have this yet */ + + /* if we have an encoding file, register it */ + if(ent->encfile) { + /* register_encoding is smart enough not to load the + * same file twice */ + if(!last_encfile || !STREQ(last_encfile, ent->encfile)) { + last_encfile = ent->encfile; + last_encoding = register_encoding(ent->encfile, 1); + } + enc = last_encoding; + } + if(ent->encfile && enc){ + if(ent->encoding && !STREQ(ent->encoding, enc->name)) { + mdvi_warning( + _("%s: %d: [%s] requested encoding `%s' does not match vector `%s'\n"), + file, lineno, ent->encfile, + ent->encoding, enc->name); + } else if(!ent->encoding) + ent->encoding = mdvi_strdup(enc->name); + } + + /* add it to the list */ + /*print_ent(ent);*/ + listh_append(&list, LIST(ent)); + ent = NULL; + } + dstring_reset(&input); + fclose(in); + + return (DviFontMapEnt *)list.head; +} + +static void free_ent(DviFontMapEnt *ent) +{ + ASSERT(ent->fontname != NULL); + mdvi_free(ent->fontname); + if(ent->psname) + mdvi_free(ent->psname); + if(ent->fontfile) + mdvi_free(ent->fontfile); + if(ent->encoding) + mdvi_free(ent->encoding); + if(ent->encfile) + mdvi_free(ent->encfile); + if(ent->fullfile) + mdvi_free(ent->fullfile); + mdvi_free(ent); +} + +void mdvi_install_fontmap(DviFontMapEnt *head) +{ + DviFontMapEnt *ent, *next; + + for(ent = head; ent; ent = next) { + /* add all the entries, overriding old ones */ + DviFontMapEnt *old; + + old = (DviFontMapEnt *) + mdvi_hash_remove(&maptable, MDVI_KEY(ent->fontname)); + if(old != NULL) { + DEBUG((DBG_FMAP, "%s: overriding fontmap entry\n", + old->fontname)); + listh_remove(&fontmaps, LIST(old)); + free_ent(old); + } + next = ent->next; + mdvi_hash_add(&maptable, MDVI_KEY(ent->fontname), + ent, MDVI_HASH_UNCHECKED); + listh_append(&fontmaps, LIST(ent)); + } +} + +static void init_static_encoding() +{ + DviEncoding *encoding; + int i; + + DEBUG((DBG_FMAP, "installing static TeX text encoding\n")); + encoding = xalloc(DviEncoding); + encoding->private = ""; + encoding->filename = ""; + encoding->name = "TeXTextEncoding"; + encoding->vector = tex_text_vector; + encoding->links = 1; + encoding->offset = 0; + mdvi_hash_create(&encoding->nametab, ENCNAME_HASH_SIZE); + for(i = 0; i < 256; i++) { + if(encoding->vector[i]) { + mdvi_hash_add(&encoding->nametab, + MDVI_KEY(encoding->vector[i]), + (DviHashKey)Int2Ptr(i), + MDVI_HASH_UNCHECKED); + } + } + ASSERT_VALUE(encodings.count, 0); + mdvi_hash_create(&enctable, ENC_HASH_SIZE); + mdvi_hash_create(&enctable_file, ENC_HASH_SIZE); + enctable_file.hash_free = file_hash_free; + mdvi_hash_add(&enctable, MDVI_KEY(encoding->name), + encoding, MDVI_HASH_UNCHECKED); + listh_prepend(&encodings, LIST(encoding)); + tex_text_encoding = encoding; + default_encoding = tex_text_encoding; +} + +static int mdvi_set_default_encoding(const char *name) +{ + DviEncoding *enc, *old; + + enc = find_encoding(name); + if(enc == NULL) + return -1; + if(enc == default_encoding) + return 0; + /* this will read it from file if necessary, + * but it can fail if the file is corrupted */ + enc = mdvi_request_encoding(name); + if(enc == NULL) + return -1; + old = default_encoding; + default_encoding = enc; + if(old != tex_text_encoding) + mdvi_release_encoding(old, 1); + return 0; +} + +static int mdvi_init_fontmaps(void) +{ + char *file; + char *line; + FILE *in; + Dstring input; + int count = 0; + char *config; + + if(fontmaps_loaded) + return 0; + /* we will only try this once */ + fontmaps_loaded = 1; + + DEBUG((DBG_FMAP, "reading fontmaps\n")); + + /* make sure the static encoding is there */ + init_static_encoding(); + + /* create the fontmap hash table */ + mdvi_hash_create(&maptable, MAP_HASH_SIZE); + + /* get the name of our configuration file */ + config = kpse_cnf_get("mdvi-config"); + if(config == NULL) + config = MDVI_DEFAULT_CONFIG; + /* let's ask kpathsea for the file first */ + file = kpse_find_file(config, kpse_program_text_format, 0); + if(file == NULL) + in = fopen(config, "rb"); + else { + in = fopen(file, "rb"); + mdvi_free(file); + } + if(in == NULL) + return -1; + dstring_init(&input); + while((line = dgets(&input, in)) != NULL) { + char *arg; + + SKIPSP(line); + if(*line < ' ' || *line == '#' || *line == '%') + continue; + if(STRNEQ(line, "fontmap", 7)) { + DviFontMapEnt *ent; + + arg = getstring(line + 7, " \t", &line); *line = 0; + DEBUG((DBG_FMAP, "%s: loading fontmap\n", arg)); + ent = mdvi_load_fontmap(arg); + if(ent == NULL) + mdvi_warning(_("%s: could not load fontmap\n"), arg); + else { + DEBUG((DBG_FMAP, + "%s: installing fontmap\n", arg)); + mdvi_install_fontmap(ent); + count++; + } + } else if(STRNEQ(line, "encoding", 8)) { + arg = getstring(line + 8, " \t", &line); *line = 0; + if(arg && *arg) + register_encoding(arg, 1); + } else if(STRNEQ(line, "default-encoding", 16)) { + arg = getstring(line + 16, " \t", &line); *line = 0; + if(mdvi_set_default_encoding(arg) < 0) + mdvi_warning(_("%s: could not set as default encoding\n"), + arg); + } else if(STRNEQ(line, "psfontpath", 10)) { + arg = getstring(line + 11, " \t", &line); *line = 0; + if(!psinitialized) + ps_init_default_paths(); + if(psfontdir) + mdvi_free(psfontdir); + psfontdir = kpse_path_expand(arg); + } else if(STRNEQ(line, "pslibpath", 9)) { + arg = getstring(line + 10, " \t", &line); *line = 0; + if(!psinitialized) + ps_init_default_paths(); + if(pslibdir) + mdvi_free(pslibdir); + pslibdir = kpse_path_expand(arg); + } else if(STRNEQ(line, "psfontmap", 9)) { + arg = getstring(line + 9, " \t", &line); *line = 0; + if(mdvi_ps_read_fontmap(arg) < 0) + mdvi_warning("%s: %s: could not read PS fontmap\n", + config, arg); + } + } + fclose(in); + dstring_reset(&input); + fontmaps_loaded = 1; + DEBUG((DBG_FMAP, "%d files installed, %d fontmaps\n", + count, fontmaps.count)); + return count; +} + +int mdvi_query_fontmap(DviFontMapInfo *info, const char *fontname) +{ + DviFontMapEnt *ent; + + if(!fontmaps_loaded && mdvi_init_fontmaps() < 0) + return -1; + ent = (DviFontMapEnt *)mdvi_hash_lookup(&maptable, MDVI_KEY(fontname)); + + if(ent == NULL) + return -1; + info->psname = ent->psname; + info->encoding = ent->encoding; + info->fontfile = ent->fontfile; + info->extend = ent->extend; + info->slant = ent->slant; + info->fullfile = ent->fullfile; + + return 0; +} + +int mdvi_add_fontmap_file(const char *name, const char *fullpath) +{ + DviFontMapEnt *ent; + + if(!fontmaps_loaded && mdvi_init_fontmaps() < 0) + return -1; + ent = (DviFontMapEnt *)mdvi_hash_lookup(&maptable, MDVI_KEY(name)); + if(ent == NULL) + return -1; + if(ent->fullfile) + mdvi_free(ent->fullfile); + ent->fullfile = mdvi_strdup(fullpath); + return 0; +} + + +void mdvi_flush_encodings(void) +{ + DviEncoding *enc; + + if(enctable.nbucks == 0) + return; + + DEBUG((DBG_FMAP, "flushing %d encodings\n", encodings.count)); + /* asked to remove all encodings */ + for(; (enc = (DviEncoding *)encodings.head); ) { + encodings.head = LIST(enc->next); + if((enc != tex_text_encoding && enc->links) || enc->links > 1) { + mdvi_warning(_("encoding vector `%s' is in use\n"), + enc->name); + } + destroy_encoding(enc); + } + /* destroy the static encoding */ + if(tex_text_encoding->nametab.buckets) + mdvi_hash_reset(&tex_text_encoding->nametab, 0); + mdvi_hash_reset(&enctable, 0); + mdvi_hash_reset(&enctable_file, 0); +} + +void mdvi_flush_fontmaps(void) +{ + DviFontMapEnt *ent; + + if(!fontmaps_loaded) + return; + + DEBUG((DBG_FMAP, "flushing %d fontmaps\n", fontmaps.count)); + for(; (ent = (DviFontMapEnt *)fontmaps.head); ) { + fontmaps.head = LIST(ent->next); + free_ent(ent); + } + mdvi_hash_reset(&maptable, 0); + fontmaps_loaded = 0; +} + +/* reading of PS fontmaps */ + +void ps_init_default_paths(void) +{ + char *kppath; + char *kfpath; + + ASSERT(psinitialized == 0); + + kppath = getenv("GS_LIB"); + kfpath = getenv("GS_FONTPATH"); + + if(kppath != NULL) + pslibdir = kpse_path_expand(kppath); + if(kfpath != NULL) + psfontdir = kpse_path_expand(kfpath); + + listh_init(&psfonts); + mdvi_hash_create(&pstable, PSMAP_HASH_SIZE); + psinitialized = 1; +} + +int mdvi_ps_read_fontmap(const char *name) +{ + char *fullname; + FILE *in; + Dstring dstr; + char *line; + int count = 0; + + if(!psinitialized) + ps_init_default_paths(); + if(pslibdir) + fullname = kpse_path_search(pslibdir, name, 1); + else + fullname = (char *)name; + in = fopen(fullname, "rb"); + if(in == NULL) { + if(fullname != name) + mdvi_free(fullname); + return -1; + } + dstring_init(&dstr); + + while((line = dgets(&dstr, in)) != NULL) { + char *name; + char *mapname; + const char *ext; + PSFontMap *ps; + + SKIPSP(line); + /* we're looking for lines of the form + * /FONT-NAME (fontfile) + * /FONT-NAME /FONT-ALIAS + */ + if(*line != '/') + continue; + name = getword(line + 1, " \t", &line); + if(*line) *line++ = 0; + mapname = getword(line, " \t", &line); + if(*line) *line++ = 0; + + if(!name || !mapname || !*name) + continue; + if(*mapname == '(') { + char *end; + + mapname++; + for(end = mapname; *end && *end != ')'; end++); + *end = 0; + } + if(!*mapname) + continue; + /* dont add `.gsf' fonts, which require a full blown + * PostScript interpreter */ + ext = file_extension(mapname); + if(ext && STREQ(ext, "gsf")) { + DEBUG((DBG_FMAP, "(ps) %s: font `%s' ignored\n", + name, mapname)); + continue; + } + ps = (PSFontMap *)mdvi_hash_lookup(&pstable, MDVI_KEY(name)); + if(ps != NULL) { + if(STREQ(ps->mapname, mapname)) + continue; + DEBUG((DBG_FMAP, + "(ps) replacing font `%s' (%s) by `%s'\n", + name, ps->mapname, mapname)); + mdvi_free(ps->mapname); + ps->mapname = mdvi_strdup(mapname); + if(ps->fullname) { + mdvi_free(ps->fullname); + ps->fullname = NULL; + } + } else { + DEBUG((DBG_FMAP, "(ps) adding font `%s' as `%s'\n", + name, mapname)); + ps = xalloc(PSFontMap); + ps->psname = mdvi_strdup(name); + ps->mapname = mdvi_strdup(mapname); + ps->fullname = NULL; + listh_append(&psfonts, LIST(ps)); + mdvi_hash_add(&pstable, MDVI_KEY(ps->psname), + ps, MDVI_HASH_UNCHECKED); + count++; + } + } + fclose(in); + dstring_reset(&dstr); + + DEBUG((DBG_FMAP, "(ps) %s: %d PostScript fonts registered\n", + fullname, count)); + return 0; +} + +void mdvi_ps_flush_fonts(void) +{ + PSFontMap *map; + + if(!psinitialized) + return; + DEBUG((DBG_FMAP, "(ps) flushing PS font map (%d) entries\n", + psfonts.count)); + mdvi_hash_reset(&pstable, 0); + for(; (map = (PSFontMap *)psfonts.head); ) { + psfonts.head = LIST(map->next); + mdvi_free(map->psname); + mdvi_free(map->mapname); + if(map->fullname) + mdvi_free(map->fullname); + mdvi_free(map); + } + listh_init(&psfonts); + if(pslibdir) { + mdvi_free(pslibdir); + pslibdir = NULL; + } + if(psfontdir) { + mdvi_free(psfontdir); + psfontdir = NULL; + } + psinitialized = 0; +} + +char *mdvi_ps_find_font(const char *psname) +{ + PSFontMap *map, *smap; + char *filename; + int recursion_limit = 32; + + DEBUG((DBG_FMAP, "(ps) resolving PS font `%s'\n", psname)); + if(!psinitialized) + return NULL; + map = (PSFontMap *)mdvi_hash_lookup(&pstable, MDVI_KEY(psname)); + if(map == NULL) + return NULL; + if(map->fullname) + return mdvi_strdup(map->fullname); + + /* is it an alias? */ + smap = map; + while(recursion_limit-- > 0 && smap && *smap->mapname == '/') + smap = (PSFontMap *)mdvi_hash_lookup(&pstable, + MDVI_KEY(smap->mapname + 1)); + if(smap == NULL) { + if(recursion_limit == 0) + DEBUG((DBG_FMAP, + "(ps) %s: possible loop in PS font map\n", + psname)); + return NULL; + } + + if(psfontdir) + filename = kpse_path_search(psfontdir, smap->mapname, 1); + else if(file_exists(map->mapname)) + filename = mdvi_strdup(map->mapname); + else + filename = NULL; + if(filename) + map->fullname = mdvi_strdup(filename); + + return filename; +} + +/* + * To get metric info for a font, we proceed as follows: + * - We try to find NAME.. + * - We query the fontmap for NAME. + * - We get back a PSNAME, and use to find the file in the PS font map. + * - We get the PSFONT file name, replace its extension by "afm" and + * lookup the file in GS's font search path. + * - We finally read the data, transform it as specified in our font map, + * and return it to the caller. The new data is left in the font metrics + * cache, so the next time it will be found at the first step (when we look + * up NAME.afm). + * + * The name `_ps_' in this function is not meant to imply that it can be + * used for Type1 fonts only. It should be usable for TrueType fonts as well. + * + * The returned metric info is subjected to the same caching mechanism as + * all the other metric data, as returned by get_font_metrics(). One should + * not modify the returned data at all, and it should be disposed with + * free_font_metrics(). + */ +TFMInfo *mdvi_ps_get_metrics(const char *fontname) +{ + TFMInfo *info; + DviFontMapInfo map; + char buffer[64]; /* to avoid mallocs */ + char *psfont; + char *basefile; + char *afmfile; + char *ext; + int baselen; + int nc; + TFMChar *ch; + double efactor; + double sfactor; + + DEBUG((DBG_FMAP, "(ps) %s: looking for metric data\n", fontname)); + info = get_font_metrics(fontname, DviFontAny, NULL); + if(info != NULL) + return info; + + /* query the fontmap */ + if(mdvi_query_fontmap(&map, fontname) < 0 || !map.psname) + return NULL; + + /* get the PS font */ + psfont = mdvi_ps_find_font(map.psname); + if(psfont == NULL) + return NULL; + DEBUG((DBG_FMAP, "(ps) %s: found as PS font `%s'\n", + fontname, psfont)); + /* replace its extension */ + basefile = strrchr(psfont, '/'); + if(basefile == NULL) + basefile = psfont; + baselen = strlen(basefile); + ext = strrchr(basefile, '.'); + if(ext != NULL) + *ext = 0; + if(baselen + 4 < 64) + afmfile = &buffer[0]; + else + afmfile = mdvi_malloc(baselen + 5); + strcpy(afmfile, basefile); + strcpy(afmfile + baselen, ".afm"); + /* we don't need this anymore */ + mdvi_free(psfont); + DEBUG((DBG_FMAP, "(ps) %s: looking for `%s'\n", + fontname, afmfile)); + /* lookup the file */ + psfont = kpse_path_search(psfontdir, afmfile, 1); + /* don't need this anymore */ + if(afmfile != &buffer[0]) + mdvi_free(afmfile); + if(psfont != NULL) { + info = get_font_metrics(fontname, DviFontAFM, psfont); + mdvi_free(psfont); + } else + info = NULL; + if(info == NULL || (!map.extend && !map.slant)) + return info; + + /* + * transform the data as prescribed -- keep in mind that `info' + * points to CACHED data, so we're modifying the metric cache + * in place. + */ + +#define DROUND(x) ((x) >= 0 ? floor((x) + 0.5) : ceil((x) - 0.5)) +#define TRANSFORM(x,y) DROUND(efactor * (x) + sfactor * (y)) + + efactor = (double)map.extend / 10000.0; + sfactor = (double)map.slant / 10000.0; + DEBUG((DBG_FMAP, "(ps) %s: applying extend=%f, slant=%f\n", + efactor, sfactor)); + + nc = info->hic - info->loc + 1; + for(ch = info->chars; ch < info->chars + nc; ch++) { + /* the AFM bounding box is: + * wx = ch->advance + * llx = ch->left + * lly = -ch->depth + * urx = ch->right + * ury = ch->height + * what we do here is transform wx, llx, and urx by + * newX = efactor * oldX + sfactor * oldY + * where for `wx' oldY = 0. Also, these numbers are all in + * TFM units (i.e. TFM's fix-words, which is just the actual + * number times 2^20, no need to do anything to it). + */ + if(ch->present) { + ch->advance = TRANSFORM(ch->advance, 0); + ch->left = TRANSFORM(ch->left, -ch->depth); + ch->right = TRANSFORM(ch->right, ch->height); + } + } + + return info; +} diff --git a/backend/dvi/mdvi-lib/fontmap.h b/backend/dvi/mdvi-lib/fontmap.h new file mode 100644 index 00000000..bb5a944d --- /dev/null +++ b/backend/dvi/mdvi-lib/fontmap.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef _MDVI_FONTMAP_H +#define _MDVI_FONTMAP_H 1 + +typedef struct _DviFontMapEnt DviFontMapEnt; +typedef struct _DviEncoding DviEncoding; + +typedef struct { + const char *psname; + const char *encoding; + const char *fontfile; + const char *fullfile; + const char *fmfile; + int fmtype; + long extend; + long slant; +} DviFontMapInfo; + +struct _DviEncoding { + DviEncoding *next; + DviEncoding *prev; + char *private; + char *filename; + char *name; + char **vector; /* table with exactly 256 strings */ + int links; + long offset; + DviHashTable nametab; +}; + +struct _DviFontMapEnt { + DviFontMapEnt *next; + DviFontMapEnt *prev; + char *private; + char *fontname; + char *psname; + char *encoding; + char *encfile; + char *fontfile; + char *fullfile; + long extend; + long slant; +}; + +#define MDVI_FMAP_SLANT(x) ((double)(x)->slant / 10000.0) +#define MDVI_FMAP_EXTEND(x) ((double)(x)->extend / 10000.0) + +extern DviEncoding *mdvi_request_encoding __PROTO((const char *)); +extern void mdvi_release_encoding __PROTO((DviEncoding *, int)); +extern int mdvi_encode_glyph __PROTO((DviEncoding *, const char *)); +extern DviFontMapEnt *mdvi_load_fontmap __PROTO((const char *)); +extern void mdvi_install_fontmap __PROTO((DviFontMapEnt *)); +extern int mdvi_load_fontmaps __PROTO((void)); +extern int mdvi_query_fontmap __PROTO((DviFontMapInfo *, const char *)); +extern void mdvi_flush_encodings __PROTO((void)); +extern void mdvi_flush_fontmaps __PROTO((void)); + +extern int mdvi_add_fontmap_file __PROTO((const char *, const char *)); + +/* PS font maps */ +extern int mdvi_ps_read_fontmap __PROTO((const char *)); +extern char *mdvi_ps_find_font __PROTO((const char *)); +extern TFMInfo *mdvi_ps_get_metrics __PROTO((const char *)); +extern void mdvi_ps_flush_fonts __PROTO((void)); + +#endif /* _MDVI_FONTMAP_H */ diff --git a/backend/dvi/mdvi-lib/fontsrch.c b/backend/dvi/mdvi-lib/fontsrch.c new file mode 100644 index 00000000..8ebaa5ca --- /dev/null +++ b/backend/dvi/mdvi-lib/fontsrch.c @@ -0,0 +1,371 @@ +/* fontsearch.c -- implements the font lookup mechanism in MDVI */ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * How this works: + * Fonts are divided into MAX_CLASS priority classes. The first + * MAX_CLASS-1 ones correspond to `real' fonts (pk, gf, vf, type1, truetype, + * etc). The last one corresponds to `metric' fonts that are used as a last + * resort (tfm, afm, ofm, ...). When a font is looked up, it is tried in a + * `high' priority class (0 being the highest priority). The priority is + * lowered until it reaches MAX_CLASS-1. Then the whole thing is repeated + * for the fallback font. When the search reaches MAX_CLASS-1, we lookup the + * original font, and then the fallback font. The search can be done + * incrementally, with several calls to mdvi_lookup_font(). If this function + * is called again to continue a search, the function assumes the previous + * font it returned was not valid, and it goes on to the next step. + * + * Reason for this: + * Some font types are quite expensive to load (e.g. Type1), so loading + * them is deferred until the last possible moment. This means that a font that + * was supposed to exist may have to be discarded. Until now, MDVI had no ability to + * "resume" a search, so in this case it would have produced an error, regardless + * of whether the offending font existed in other formats. + * Also, given the large number of font types supported by MDVI, some mechanism + * was necessary to bring some order into the chaos. + * + * This mechanism fixes these two problems. For the first one, a search can + * be "resumed" and all the font formats tried for the missing font, and + * again for the fallback font (see above). As for the second, the + * hierarchical division in classes gives a lot of flexibility in how the + * fonts are configured. + */ + +#include +#include "mdvi.h" + +#define HAVE_PROTOTYPES 1 +#include +#include + +struct _DviFontClass { + DviFontClass *next; + DviFontClass *prev; + DviFontInfo info; + int links; + int id; +}; + +char *_mdvi_fallback_font = MDVI_FALLBACK_FONT; + +/* this leaves classes 0 and 1 for `real' fonts */ +#define MAX_CLASS 3 +static ListHead font_classes[MAX_CLASS]; +static int initialized = 0; + +static void init_font_classes(void) +{ + int i; + + for(i = 0; i < MAX_CLASS; i++) + listh_init(&font_classes[i]); + initialized = 1; +} + +int mdvi_get_font_classes(void) +{ + return (MAX_CLASS - 2); +} + +char **mdvi_list_font_class(int klass) +{ + char **list; + int i, n; + DviFontClass *fc; + + if(klass == -1) + klass = MAX_CLASS-1; + if(klass < 0 || klass >= MAX_CLASS) + return NULL; + n = font_classes[klass].count; + list = xnalloc(char *, n + 1); + fc = (DviFontClass *)font_classes[klass].head; + for(i = 0; i < n; fc = fc->next, i++) { + list[i] = mdvi_strdup(fc->info.name); + } + list[i] = NULL; + return list; +} + +int mdvi_register_font_type(DviFontInfo *info, int klass) +{ + DviFontClass *fc; + + if(klass == -1) + klass = MAX_CLASS-1; + if(klass < 0 || klass >= MAX_CLASS) + return -1; + if(!initialized) + init_font_classes(); + fc = xalloc(struct _DviFontClass); + fc->links = 0; + fc->id = klass; + fc->info.name = mdvi_strdup(info->name); + fc->info.scalable = info->scalable; + fc->info.load = info->load; + fc->info.getglyph = info->getglyph; + fc->info.shrink0 = info->shrink0; + fc->info.shrink1 = info->shrink1; + fc->info.freedata = info->freedata; + fc->info.reset = info->reset; + fc->info.lookup = info->lookup; + fc->info.kpse_type = info->kpse_type; + listh_append(&font_classes[klass], LIST(fc)); + return 0; +} + +int mdvi_unregister_font_type(const char *name, int klass) +{ + DviFontClass *fc; + int k; + + if(klass == -1) + klass = MAX_CLASS - 1; + + if(klass >= 0 && klass < MAX_CLASS) { + k = klass; + LIST_FOREACH(fc, DviFontClass, &font_classes[k]) { + if(STREQ(fc->info.name, name)) + break; + } + } else if(klass < 0) { + for(k = 0; k < MAX_CLASS; k++) { + LIST_FOREACH(fc, DviFontClass, &font_classes[k]) { + if(STREQ(fc->info.name, name)) + break; + } + if(fc) break; + } + } else + return -1; + + if(fc == NULL || fc->links) + return -1; + /* remove it */ + listh_remove(&font_classes[k], LIST(fc)); + + /* and destroy it */ + mdvi_free(fc->info.name); + mdvi_free(fc); + return 0; +} + +static char *lookup_font(DviFontClass *ptr, const char *name, Ushort *h, Ushort *v) +{ + char *filename; + + /* + * If the font type registered a function to do the lookup, use that. + * Otherwise we use kpathsea. + */ + if(ptr->info.lookup) + filename = ptr->info.lookup(name, h, v); + else if(ptr->info.kpse_type <= kpse_any_glyph_format) { + kpse_glyph_file_type type; + + filename = kpse_find_glyph(name, Max(*h, *v), + ptr->info.kpse_type, &type); + /* if kpathsea returned a fallback font, reject it */ + if(filename && type.source == kpse_glyph_source_fallback) { + mdvi_free(filename); + filename = NULL; + } else if(filename) + *h = *v = type.dpi; + } else + filename = kpse_find_file(name, ptr->info.kpse_type, 1); + return filename; +} + +/* + * Class MAX_CLASS-1 is special: it consists of `metric' fonts that should + * be tried as a last resort + */ +char *mdvi_lookup_font(DviFontSearch *search) +{ + int kid; + int k; + DviFontClass *ptr; + DviFontClass *last; + char *filename = NULL; + const char *name; + Ushort hdpi, vdpi; + + if(search->id < 0) + return NULL; + + if(search->curr == NULL) { + /* this is the initial search */ + name = search->wanted_name; + hdpi = search->hdpi; + vdpi = search->vdpi; + kid = 0; + last = NULL; + } else { + name = search->actual_name; + hdpi = search->actual_hdpi; + vdpi = search->actual_vdpi; + kid = search->id; + last = search->curr; + } + + ptr = NULL; +again: + /* try all classes except MAX_CLASS-1 */ + for(k = kid; !filename && k < MAX_CLASS-1; k++) { + if(last == NULL) + ptr = (DviFontClass *)font_classes[k].head; + else + ptr = last->next; + while(ptr) { + DEBUG((DBG_FONTS, "%d: trying `%s' at (%d,%d)dpi as `%s'\n", + k, name, hdpi, vdpi, ptr->info.name)); + /* lookup the font in this class */ + filename = lookup_font(ptr, name, &hdpi, &vdpi); + if(filename) + break; + ptr = ptr->next; + } + last = NULL; + } + if(filename != NULL) { + search->id = k-1; + search->curr = ptr; + search->actual_name = name; + search->actual_hdpi = hdpi; + search->actual_vdpi = vdpi; + search->info = &ptr->info; + ptr->links++; + return filename; + } + + if(kid < MAX_CLASS - 1 && !STREQ(name, _mdvi_fallback_font)) { + mdvi_warning("font `%s' at %dx%d not found, trying `%s' instead\n", + name, hdpi, vdpi, _mdvi_fallback_font); + name = _mdvi_fallback_font; + kid = 0; + goto again; + } + + /* we tried the fallback font, and all the `real' classes. Let's + * try the `metric' class now */ + name = search->wanted_name; + hdpi = search->hdpi; + vdpi = search->vdpi; + if(kid == MAX_CLASS-1) { + /* we were looking into this class from the beginning */ + if(last == NULL) { + /* no more fonts to try */ + return NULL; + } + ptr = last->next; + } else { + mdvi_warning("font `%s' not found, trying metric files instead\n", + name); + ptr = (DviFontClass *)font_classes[MAX_CLASS-1].head; + } + +metrics: + while(ptr) { + DEBUG((DBG_FONTS, "metric: trying `%s' at (%d,%d)dpi as `%s'\n", + name, hdpi, vdpi, ptr->info.name)); + filename = lookup_font(ptr, name, &hdpi, &vdpi); + if(filename) + break; + ptr = ptr->next; + } + if(filename != NULL) { + if(STREQ(name, _mdvi_fallback_font)) + search->id = MAX_CLASS; + else + search->id = MAX_CLASS - 1; + search->curr = ptr; + search->actual_name = name; + search->actual_hdpi = hdpi; + search->actual_vdpi = vdpi; + search->info = &ptr->info; + ptr->links++; + return filename; + } + if(!STREQ(name, _mdvi_fallback_font)) { + mdvi_warning("metric file for `%s' not found, trying `%s' instead\n", + name, _mdvi_fallback_font); + name = _mdvi_fallback_font; + ptr = (DviFontClass *)font_classes[MAX_CLASS-1].head; + goto metrics; + } + + search->id = -1; + search->actual_name = NULL; + + /* tough luck, nothing found */ + return NULL; +} + +/* called by `font_reference' to do the initial lookup */ +DviFont *mdvi_add_font(const char *name, Int32 sum, + int hdpi, int vdpi, Int32 scale) +{ + DviFont *font; + + font = xalloc(DviFont); + font->fontname = mdvi_strdup(name); + SEARCH_INIT(font->search, font->fontname, hdpi, vdpi); + font->filename = mdvi_lookup_font(&font->search); + if(font->filename == NULL) { + /* this answer is final */ + mdvi_free(font->fontname); + mdvi_free(font); + return NULL; + } + font->hdpi = font->search.actual_hdpi; + font->vdpi = font->search.actual_vdpi; + font->scale = scale; + font->design = 0; + font->checksum = sum; + font->type = 0; + font->links = 0; + font->loc = 0; + font->hic = 0; + font->in = NULL; + font->chars = NULL; + font->subfonts = NULL; + + return font; +} + +int mdvi_font_retry(DviParams *params, DviFont *font) +{ + /* try the search again */ + char *filename; + + ASSERT(font->search.curr != NULL); + /* we won't be using this class anymore */ + font->search.curr->links--; + + filename = mdvi_lookup_font(&font->search); + if(filename == NULL) + return -1; + mdvi_free(font->filename); + font->filename = filename; + /* copy the new information */ + font->hdpi = font->search.actual_hdpi; + font->vdpi = font->search.actual_vdpi; + + return 0; +} diff --git a/backend/dvi/mdvi-lib/gf.c b/backend/dvi/mdvi-lib/gf.c new file mode 100644 index 00000000..00607ff6 --- /dev/null +++ b/backend/dvi/mdvi-lib/gf.c @@ -0,0 +1,395 @@ +/* gf.c - GF font support */ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* functions to read GF fonts */ + +#include +#include +#include "common.h" +#include "mdvi.h" +#include "private.h" + +/* opcodes */ + +#define GF_PAINT0 0 +#define GF_PAINT1 64 +#define GF_PAINT2 65 +#define GF_PAINT3 66 +#define GF_BOC 67 +#define GF_BOC1 68 +#define GF_EOC 69 +#define GF_SKIP0 70 +#define GF_SKIP1 71 +#define GF_SKIP2 72 +#define GF_SKIP3 73 +#define GF_NEW_ROW_0 74 +#define GF_NEW_ROW_1 75 +#define GF_NEW_ROW_MAX 238 +#define GF_XXX1 239 +#define GF_XXX2 240 +#define GF_XXX3 241 +#define GF_XXX4 242 +#define GF_YYY 243 +#define GF_NOOP 244 +#define GF_LOC 245 +#define GF_LOC0 246 +#define GF_PRE 247 +#define GF_POST 248 +#define GF_POST_POST 249 + +#define GF_ID 131 +#define GF_TRAILER 223 + +#define BLACK 1 +#define WHITE 0 + +static int gf_load_font __PROTO((DviParams *, DviFont *)); +static int gf_font_get_glyph __PROTO((DviParams *, DviFont *, int)); + +/* only symbol exported by this file */ +DviFontInfo gf_font_info = { + "GF", + 0, /* scaling not supported natively */ + gf_load_font, + gf_font_get_glyph, + mdvi_shrink_glyph, + mdvi_shrink_glyph_grey, + NULL, /* free */ + NULL, /* reset */ + NULL, /* lookup */ + kpse_gf_format, + NULL +}; + +static int gf_read_bitmap(FILE *p, DviFontChar *ch) +{ + int op; + int min_n, max_n; + int min_m, max_m; + int paint_switch; + int x, y; + int bpl; + Int32 par; + BmUnit *line; + BITMAP *map; + + fseek(p, (long)ch->offset, SEEK_SET); + op = fuget1(p); + if(op == GF_BOC) { + /* skip character code */ + fuget4(p); + /* skip pointer */ + fuget4(p); + min_m = fsget4(p); + max_m = fsget4(p); + min_n = fsget4(p); + max_n = fsget4(p); + } else if(op == GF_BOC1) { + /* skip character code */ + fuget1(p); + min_m = fuget1(p); /* this is max_m - min_m */ + max_m = fuget1(p); + min_n = fuget1(p); /* this is max_n - min_n */ + max_n = fuget1(p); + min_m = max_m - min_m; + min_n = max_n - min_n; + } else { + mdvi_error(_("GF: invalid opcode %d in character %d\n"), + op, ch->code); + return -1; + } + + ch->x = -min_m; + ch->y = max_n; + ch->width = max_m - min_m + 1; + ch->height = max_n - min_n + 1; + map = bitmap_alloc(ch->width, ch->height); + + ch->glyph.data = map; + ch->glyph.x = ch->x; + ch->glyph.y = ch->y; + ch->glyph.w = ch->width; + ch->glyph.h = ch->height; + +#define COLOR(x) ((x) ? "BLACK" : "WHITE") + + paint_switch = WHITE; + x = y = 0; + line = map->data; + bpl = map->stride; + DEBUG((DBG_BITMAPS, "(gf) reading character %d\n", ch->code)); + while((op = fuget1(p)) != GF_EOC) { + Int32 n; + + if(feof(p)) + break; + if(op == GF_PAINT0) { + DEBUG((DBG_BITMAPS, "(gf) Paint0 %s -> %s\n", + COLOR(paint_switch), COLOR(!paint_switch))); + paint_switch = !paint_switch; + } else if(op <= GF_PAINT3) { + if(op < GF_PAINT1) + par = op; + else + par = fugetn(p, op - GF_PAINT1 + 1); + if(y >= ch->height || x + par >= ch->width) + goto toobig; + /* paint everything between columns x and x + par - 1 */ + DEBUG((DBG_BITMAPS, "(gf) Paint %d %s from (%d,%d)\n", + par, COLOR(paint_switch), x, y)); + if(paint_switch == BLACK) + bitmap_paint_bits(line + (x / BITMAP_BITS), + x % BITMAP_BITS, par); + paint_switch = !paint_switch; + x += par; + } else if(op >= GF_NEW_ROW_0 && op <= GF_NEW_ROW_MAX) { + y++; + line = bm_offset(line, bpl); + x = op - GF_NEW_ROW_0; + paint_switch = BLACK; + DEBUG((DBG_BITMAPS, "(gf) new_row_%d\n", x)); + } else switch(op) { + case GF_SKIP0: + y++; + line = bm_offset(line, bpl); + x = 0; + paint_switch = WHITE; + DEBUG((DBG_BITMAPS, "(gf) skip_0\n")); + break; + case GF_SKIP1: + case GF_SKIP2: + case GF_SKIP3: + par = fugetn(p, op - GF_SKIP1 + 1); + y += par + 1; + line = bm_offset(line, (par + 1) * bpl); + x = 0; + paint_switch = WHITE; + DEBUG((DBG_BITMAPS, "(gf) skip_%d\n", op - GF_SKIP1)); + break; + case GF_XXX1: + case GF_XXX2: + case GF_XXX3: + case GF_XXX4: { +#ifndef NODEBUG + char *s; + + s = read_string(p, op - GF_XXX1 + 1, NULL, 0); + DEBUG((DBG_SPECIAL, "(gf) Character %d: Special \"%s\"\n", + ch->code, s)); + mdvi_free(s); +#else + n = fugetn(p, op - GF_XXX1 + 1); + fseek(p, (long)n, SEEK_CUR); +#endif + break; + } + case GF_YYY: + n = fuget4(p); + DEBUG((DBG_SPECIAL, "(gf) Character %d: MF special %u\n", + ch->code, n)); + break; + case GF_NOOP: + DEBUG((DBG_BITMAPS, "(gf) no_op\n")); + break; + default: + mdvi_error(_("(gf) Character %d: invalid opcode %d\n"), + ch->code, op); + goto error; + } + /* chech that we're still inside the bitmap */ + if(x > ch->width || y > ch->height) + goto toobig; + DEBUG((DBG_BITMAPS, "(gf) curr_loc @ (%d,%d)\n", x, y)); + } + + if(op != GF_EOC) + goto error; + DEBUG((DBG_BITMAPS, "(gf) end of character %d\n", ch->code)); + return 0; + +toobig: + mdvi_error(_("(gf) character %d has an incorrect bounding box\n"), + ch->code); +error: + bitmap_destroy(map); + ch->glyph.data = NULL; + return -1; +} + +static int gf_load_font(DviParams *unused, DviFont *font) +{ + int i; + int n; + int loc; + int hic; + FILE *p; + Int32 word; + int op; + long alpha, beta, z; +#ifndef NODEBUG + char s[256]; +#endif + + p = font->in; + + /* check preamble */ + loc = fuget1(p); hic = fuget1(p); + if(loc != GF_PRE || hic != GF_ID) + goto badgf; + loc = fuget1(p); +#ifndef NODEBUG + for(i = 0; i < loc; i++) + s[i] = fuget1(p); + s[i] = 0; + DEBUG((DBG_FONTS, "(gf) %s: %s\n", font->fontname, s)); +#else + fseek(p, (long)loc, SEEK_CUR); +#endif + /* now read character locators in postamble */ + if(fseek(p, (long)-1, SEEK_END) == -1) + return -1; + + n = 0; + while((op = fuget1(p)) == GF_TRAILER) { + if(fseek(p, (long)-2, SEEK_CUR) < 0) + break; + n++; + } + if(op != GF_ID || n < 4) + goto badgf; + /* get the pointer to the postamble */ + fseek(p, (long)-5, SEEK_CUR); + op = fuget4(p); + /* jump to it */ + fseek(p, (long)op, SEEK_SET); + if(fuget1(p) != GF_POST) + goto badgf; + /* skip pointer to last EOC */ + fuget4(p); + /* get the design size */ + font->design = fuget4(p); + /* the checksum */ + word = fuget4(p); + if(word && font->checksum && font->checksum != word) { + mdvi_warning(_("%s: bad checksum (expected %u, found %u)\n"), + font->fontname, font->checksum, word); + } else if(!font->checksum) + font->checksum = word; + /* skip pixels per point ratio */ + fuget4(p); + fuget4(p); + font->chars = xnalloc(DviFontChar, 256); + for(loc = 0; loc < 256; loc++) + font->chars[loc].offset = 0; + /* skip glyph "bounding box" */ + fseek(p, (long)16, SEEK_CUR); + loc = 256; + hic = -1; + TFMPREPARE(font->scale, z, alpha, beta); + while((op = fuget1(p)) != GF_POST_POST) { + DviFontChar *ch; + int cc; + + /* get the character code */ + cc = fuget1(p); + if(cc < loc) + loc = cc; + if(cc > hic) + hic = cc; + ch = &font->chars[cc]; + switch(op) { + case GF_LOC: + fsget4(p); /* skip dx */ + fsget4(p); /* skip dy */ + break; + case GF_LOC0: + fuget1(p); /* skip dx */ + /* dy assumed 0 */ + break; + default: + mdvi_error(_("%s: junk in postamble\n"), font->fontname); + goto error; + } + ch->code = cc; + ch->tfmwidth = fuget4(p); + ch->tfmwidth = TFMSCALE(ch->tfmwidth, z, alpha, beta); + ch->offset = fuget4(p); + if(ch->offset == -1) + ch->offset = 0; + /* initialize the rest of the glyph information */ + ch->x = 0; + ch->y = 0; + ch->width = 0; + ch->height = 0; + ch->glyph.data = NULL; + ch->shrunk.data = NULL; + ch->grey.data = NULL; + ch->flags = 0; + ch->loaded = 0; + } + + if(op != GF_POST_POST) + goto badgf; + + if(loc > 0 || hic < 255) { + /* shrink to optimal size */ + memmove(font->chars, font->chars + loc, + (hic - loc + 1) * sizeof(DviFontChar)); + font->chars = xresize(font->chars, + DviFontChar, hic - loc + 1); + } + font->loc = loc; + font->hic = hic; + + return 0; + +badgf: + mdvi_error(_("%s: File corrupted, or not a GF file\n"), font->fontname); +error: + if(font->chars) { + mdvi_free(font->chars); + font->chars = NULL; + } + font->loc = font->hic = 0; + return -1; +} + +static int gf_font_get_glyph(DviParams *params, DviFont *font, int code) +{ + DviFontChar *ch; + + if(code < font->loc || code > font->hic || !font->chars) + return -1; + ch = &font->chars[code - font->loc]; + + if(!ch->loaded) { + if(ch->offset == 0) + return -1; + DEBUG((DBG_GLYPHS, "(gf) %s: loading GF glyph for character %d\n", + font->fontname, code)); + if(font->in == NULL && font_reopen(font) < 0) + return -1; + if(fseek(font->in, ch->offset, SEEK_SET) == -1) + return -1; + if(gf_read_bitmap(font->in, ch) < 0) + return -1; + ch->loaded = 1; + } + return 0; +} diff --git a/backend/dvi/mdvi-lib/hash.c b/backend/dvi/mdvi-lib/hash.c new file mode 100644 index 00000000..d359e3c8 --- /dev/null +++ b/backend/dvi/mdvi-lib/hash.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "mdvi.h" + +/* simple hash tables for MDVI */ + + +struct _DviHashBucket { + DviHashBucket *next; + DviHashKey key; + Ulong hvalue; + void *data; +}; + +static Ulong hash_string(DviHashKey key) +{ + Uchar *p; + Ulong h, g; + + for(h = 0, p = (Uchar *)key; *p; p++) { + h = (h << 4UL) + *p; + if((g = h & 0xf0000000L) != 0) { + h ^= (g >> 24UL); + h ^= g; + } + } + + return h; +} + +static int hash_compare(DviHashKey k1, DviHashKey k2) +{ + return strcmp((char *)k1, (char *)k2); +} + +void mdvi_hash_init(DviHashTable *hash) +{ + hash->buckets = NULL; + hash->nbucks = 0; + hash->nkeys = 0; + hash->hash_func = NULL; + hash->hash_comp = NULL; + hash->hash_free = NULL; +} + +void mdvi_hash_create(DviHashTable *hash, int size) +{ + int i; + + hash->nbucks = size; + hash->buckets = xnalloc(DviHashBucket *, size); + for(i = 0; i < size; i++) + hash->buckets[i] = NULL; + hash->hash_func = hash_string; + hash->hash_comp = hash_compare; + hash->hash_free = NULL; + hash->nkeys = 0; +} + +static DviHashBucket *hash_find(DviHashTable *hash, DviHashKey key) +{ + Ulong hval; + DviHashBucket *buck; + + hval = (hash->hash_func(key) % hash->nbucks); + + for(buck = hash->buckets[hval]; buck; buck = buck->next) + if(hash->hash_comp(buck->key, key) == 0) + break; + return buck; +} + +/* Neither keys nor data are duplicated */ +int mdvi_hash_add(DviHashTable *hash, DviHashKey key, void *data, int rep) +{ + DviHashBucket *buck = NULL; + Ulong hval; + + if(rep != MDVI_HASH_UNCHECKED) { + buck = hash_find(hash, key); + if(buck != NULL) { + if(buck->data == data) + return 0; + if(rep == MDVI_HASH_UNIQUE) + return -1; + if(hash->hash_free != NULL) + hash->hash_free(buck->key, buck->data); + } + } + if(buck == NULL) { + buck = xalloc(DviHashBucket); + buck->hvalue = hash->hash_func(key); + hval = (buck->hvalue % hash->nbucks); + buck->next = hash->buckets[hval]; + hash->buckets[hval] = buck; + hash->nkeys++; + } + + /* save key and data */ + buck->key = key; + buck->data = data; + + return 0; +} + +void *mdvi_hash_lookup(DviHashTable *hash, DviHashKey key) +{ + DviHashBucket *buck = hash_find(hash, key); + + return buck ? buck->data : NULL; +} + +static DviHashBucket *hash_remove(DviHashTable *hash, DviHashKey key) +{ + DviHashBucket *buck, *last; + Ulong hval; + + hval = hash->hash_func(key); + hval %= hash->nbucks; + + for(last = NULL, buck = hash->buckets[hval]; buck; buck = buck->next) { + if(hash->hash_comp(buck->key, key) == 0) + break; + last = buck; + } + if(buck == NULL) + return NULL; + if(last) + last->next = buck->next; + else + hash->buckets[hval] = buck->next; + hash->nkeys--; + return buck; +} + +void *mdvi_hash_remove(DviHashTable *hash, DviHashKey key) +{ + DviHashBucket *buck = hash_remove(hash, key); + void *data = NULL; + + if(buck) { + data = buck->data; + mdvi_free(buck); + } + return data; +} + +void *mdvi_hash_remove_ptr(DviHashTable *hash, DviHashKey key) +{ + DviHashBucket *buck, *last; + Ulong hval; + void *ptr; + + hval = hash->hash_func(key); + hval %= hash->nbucks; + + for(last = NULL, buck = hash->buckets[hval]; buck; buck = buck->next) { + if(buck->key == key) + break; + last = buck; + } + if(buck == NULL) + return NULL; + if(last) + last->next = buck->next; + else + hash->buckets[hval] = buck->next; + hash->nkeys--; + /* destroy the bucket */ + ptr = buck->data; + mdvi_free(buck); + return ptr; +} + +int mdvi_hash_destroy_key(DviHashTable *hash, DviHashKey key) +{ + DviHashBucket *buck = hash_remove(hash, key); + + if(buck == NULL) + return -1; + if(hash->hash_free) + hash->hash_free(buck->key, buck->data); + mdvi_free(buck); + return 0; +} + +void mdvi_hash_reset(DviHashTable *hash, int reuse) +{ + int i; + DviHashBucket *buck; + + /* remove all keys in the hash table */ + for(i = 0; i < hash->nbucks; i++) { + for(; (buck = hash->buckets[i]); ) { + hash->buckets[i] = buck->next; + if(hash->hash_free) + hash->hash_free(buck->key, buck->data); + mdvi_free(buck); + } + } + hash->nkeys = 0; + if(!reuse && hash->buckets) { + mdvi_free(hash->buckets); + hash->buckets = NULL; + hash->nbucks = 0; + } /* otherwise, it is left empty, ready to be reused */ +} diff --git a/backend/dvi/mdvi-lib/hash.h b/backend/dvi/mdvi-lib/hash.h new file mode 100644 index 00000000..b10afd60 --- /dev/null +++ b/backend/dvi/mdvi-lib/hash.h @@ -0,0 +1,49 @@ +#ifndef MDVI_HASH +#define MDVI_HASH + +/* Hash tables */ + + +typedef struct _DviHashBucket DviHashBucket; +typedef struct _DviHashTable DviHashTable; + +/* + * Hash tables + */ + +typedef Uchar *DviHashKey; +#define MDVI_KEY(x) ((DviHashKey)(x)) + +typedef Ulong (*DviHashFunc) __PROTO((DviHashKey key)); +typedef int (*DviHashComp) __PROTO((DviHashKey key1, DviHashKey key2)); +typedef void (*DviHashFree) __PROTO((DviHashKey key, void *data)); + + +struct _DviHashTable { + DviHashBucket **buckets; + int nbucks; + int nkeys; + DviHashFunc hash_func; + DviHashComp hash_comp; + DviHashFree hash_free; +}; +#define MDVI_EMPTY_HASH_TABLE {NULL, 0, 0, NULL, NULL, NULL} + +#define MDVI_HASH_REPLACE 0 +#define MDVI_HASH_UNIQUE 1 +#define MDVI_HASH_UNCHECKED 2 + +extern void mdvi_hash_init __PROTO((DviHashTable *)); +extern void mdvi_hash_create __PROTO((DviHashTable *, int)); +extern int mdvi_hash_add __PROTO((DviHashTable *, DviHashKey, void *, int)); +extern int mdvi_hash_destroy_key __PROTO((DviHashTable *, DviHashKey)); +extern void mdvi_hash_reset __PROTO((DviHashTable *, int)); +extern void *mdvi_hash_lookup __PROTO((DviHashTable *, DviHashKey)); +extern void *mdvi_hash_remove __PROTO((DviHashTable *, DviHashKey)); +extern void *mdvi_hash_remove_ptr __PROTO((DviHashTable *, DviHashKey)); + +#define mdvi_hash_flush(h) mdvi_hash_reset((h), 1) +#define mdvi_hash_destroy(h) mdvi_hash_reset((h), 0) + +#endif + diff --git a/backend/dvi/mdvi-lib/list.c b/backend/dvi/mdvi-lib/list.c new file mode 100644 index 00000000..eb73a178 --- /dev/null +++ b/backend/dvi/mdvi-lib/list.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "common.h" + +void listh_init(ListHead *head) +{ + head->head = head->tail = NULL; + head->count = 0; +} + +void listh_prepend(ListHead *head, List *list) +{ + list->prev = NULL; + list->next = head->head; + if(head->head) + head->head->prev = list; + head->head = list; + if(!head->tail) + head->tail = list; + head->count++; +} + +void listh_append(ListHead *head, List *list) +{ + list->next = NULL; + list->prev = head->tail; + if(head->tail) + head->tail->next = list; + else + head->head = list; + head->tail = list; + head->count++; +} + +void listh_add_before(ListHead *head, List *at, List *list) +{ + if(at == head->head || head->head == NULL) + listh_prepend(head, list); + else { + list->next = at; + list->prev = at->prev; + at->prev = list; + head->count++; + } +} + +void listh_add_after(ListHead *head, List *at, List *list) +{ + if(at == head->tail || !head->tail) + listh_append(head, list); + else { + list->prev = at; + list->next = at->next; + at->next = list; + head->count++; + } +} + +void listh_remove(ListHead *head, List *list) +{ + if(list == head->head) { + head->head = list->next; + if(head->head) + head->head->prev = NULL; + } else if(list == head->tail) { + head->tail = list->prev; + if(head->tail) + head->tail->next = NULL; + } else { + list->next->prev = list->prev; + list->prev->next = list->next; + } + if(--head->count == 0) + head->head = head->tail = NULL; +} + +void listh_concat(ListHead *h1, ListHead *h2) +{ + if(h2->head == NULL) + ; /* do nothing */ + else if(h1->tail == NULL) + h1->head = h2->head; + else { + h1->tail->next = h2->head; + h2->head->prev = h1->tail; + } + h1->tail = h2->tail; + h1->count += h2->count; +} + +void listh_catcon(ListHead *h1, ListHead *h2) +{ + if(h2->head == NULL) + ; /* do nothing */ + else if(h1->head == NULL) + h1->tail = h2->tail; + else { + h1->head->prev = h2->tail; + h2->tail->next = h1->head; + } + h1->head = h2->head; + h1->count += h2->count; +} diff --git a/backend/dvi/mdvi-lib/mdvi.h b/backend/dvi/mdvi-lib/mdvi.h new file mode 100644 index 00000000..327e61fe --- /dev/null +++ b/backend/dvi/mdvi-lib/mdvi.h @@ -0,0 +1,623 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef _MDVI_DVI_H +#define _MDVI_DVI_H 1 + +#include +#include +#include + +#include "sysdeps.h" +#include "bitmap.h" +#include "common.h" +#include "defaults.h" +#include "dviopcodes.h" + +typedef struct _DviGlyph DviGlyph; +typedef struct _DviDevice DviDevice; +typedef struct _DviFontChar DviFontChar; +typedef struct _DviFontRef DviFontRef; +typedef struct _DviFontInfo DviFontInfo; +typedef struct _DviFont DviFont; +typedef struct _DviState DviState; +typedef struct _DviPageSpec *DviPageSpec; +typedef struct _DviParams DviParams; +typedef struct _DviBuffer DviBuffer; +typedef struct _DviContext DviContext; +typedef struct _DviRange DviRange; +typedef struct _DviColorPair DviColorPair; +typedef struct _DviSection DviSection; +typedef struct _TFMChar TFMChar; +typedef struct _TFMInfo TFMInfo; +typedef struct _DviFontSearch DviFontSearch; +/* this is an opaque type */ +typedef struct _DviFontClass DviFontClass; + +typedef void (*DviFreeFunc) __PROTO((void *)); +typedef void (*DviFree2Func) __PROTO((void *, void *)); + +typedef Ulong DviColor; + +#ifdef TRUE +#undef TRUE +#endif +#ifdef FALSE +#undef FALSE +#endif + +typedef enum { + FALSE = 0, + TRUE = 1 +} DviBool; + +#include "hash.h" +#include "paper.h" + +/* + * information about a page: + * pagenum[0] = offset to BOP + * pagenum[1], ..., pagenum[10] = TeX \counters + */ +typedef long PageNum[11]; + +/* this structure contains the platform-specific information + * required to interpret a DVI file */ + +typedef void (*DviGlyphDraw) __PROTO((DviContext *context, + DviFontChar *glyph, + int x, int y)); + +typedef void (*DviRuleDraw) __PROTO((DviContext *context, + int x, int y, + Uint width, Uint height, int fill)); + +typedef int (*DviColorScale) __PROTO((void *device_data, + Ulong *pixels, + int npixels, + Ulong foreground, + Ulong background, + double gamma, + int density)); +typedef void *(*DviCreateImage) __PROTO((void *device_data, + Uint width, + Uint height, + Uint bpp)); +typedef void (*DviFreeImage) __PROTO((void *image)); +typedef void (*DviPutPixel) __PROTO((void *image, int x, int y, Ulong color)); +typedef void (*DviDevDestroy) __PROTO((void *data)); +typedef void (*DviRefresh) __PROTO((DviContext *dvi, void *device_data)); +typedef void (*DviSetColor) __PROTO((void *device_data, Ulong, Ulong)); +typedef void (*DviPSDraw) __PROTO((DviContext *context, + const char *filename, + int x, int y, + Uint width, Uint height)); + +struct _DviDevice { + DviGlyphDraw draw_glyph; + DviRuleDraw draw_rule; + DviColorScale alloc_colors; + DviCreateImage create_image; + DviFreeImage free_image; + DviPutPixel put_pixel; + DviDevDestroy dev_destroy; + DviRefresh refresh; + DviSetColor set_color; + DviPSDraw draw_ps; + void * device_data; +}; + +/* + * Fonts + */ + +#include "fontmap.h" + +struct _TFMChar { + Int32 present; + Int32 advance; /* advance */ + Int32 height; /* ascent */ + Int32 depth; /* descent */ + Int32 left; /* leftSideBearing */ + Int32 right; /* rightSideBearing */ +}; + +struct _TFMInfo { + int type; /* DviFontAFM, DviFontTFM, DviFontOFM */ + Uint32 checksum; + Uint32 design; + int loc; + int hic; + char coding[64]; + char family[64]; + TFMChar *chars; +}; + +struct _DviGlyph { + short x, y; /* origin */ + Uint w, h; /* dimensions */ + void *data; /* bitmap or XImage */ +}; + +typedef void (*DviFontShrinkFunc) + __PROTO((DviContext *, DviFont *, DviFontChar *, DviGlyph *)); +typedef int (*DviFontLoadFunc) __PROTO((DviParams *, DviFont *)); +typedef int (*DviFontGetGlyphFunc) __PROTO((DviParams *, DviFont *, int)); +typedef void (*DviFontFreeFunc) __PROTO((DviFont *)); +typedef void (*DviFontResetFunc) __PROTO((DviFont *)); +typedef char *(*DviFontLookupFunc) __PROTO((const char *, Ushort *, Ushort *)); +typedef int (*DviFontEncodeFunc) __PROTO((DviParams *, DviFont *, DviEncoding *)); + +struct _DviFontInfo { + char *name; /* human-readable format identifying string */ + int scalable; /* does it support scaling natively? */ + DviFontLoadFunc load; + DviFontGetGlyphFunc getglyph; + DviFontShrinkFunc shrink0; + DviFontShrinkFunc shrink1; + DviFontFreeFunc freedata; + DviFontResetFunc reset; + DviFontLookupFunc lookup; + int kpse_type; + void * private; +}; + +struct _DviFontChar { + Uint32 offset; + Int16 code; /* format-dependent, not used by MDVI */ + Int16 width; + Int16 height; + Int16 x; + Int16 y; + Int32 tfmwidth; + Ushort flags; +#ifdef __STRICT_ANSI__ + Ushort loaded; + Ushort missing; +#else + Ushort loaded : 1, + missing : 1; +#endif + Ulong fg; + Ulong bg; + BITMAP *glyph_data; + /* data for shrunk bitimaps */ + DviGlyph glyph; + DviGlyph shrunk; + DviGlyph grey; +}; + +struct _DviFontRef { + DviFontRef *next; + DviFont *ref; + Int32 fontid; +}; + +typedef enum { + DviFontAny = -1, + DviFontPK = 0, + DviFontGF = 1, + DviFontVF = 2, + DviFontTFM = 3, + DviFontT1 = 4, + DviFontTT = 5, + DviFontAFM = 6, + DviFontOFM = 7 +} DviFontType; + +struct _DviFontSearch { + int id; + Ushort hdpi; + Ushort vdpi; + Ushort actual_hdpi; + Ushort actual_vdpi; + const char *wanted_name; + const char *actual_name; + DviFontClass *curr; + DviFontInfo *info; +}; + +/* this is a kludge, I know */ +#define ISVIRTUAL(font) ((font)->search.info->getglyph == NULL) +#define SEARCH_DONE(s) ((s).id < 0) +#define SEARCH_INIT(s, name, h, v) do { \ + (s).id = 0; \ + (s).curr = NULL; \ + (s).hdpi = (h); \ + (s).vdpi = (v); \ + (s).wanted_name = (name); \ + (s).actual_name = NULL; \ + } while(0) + +struct _DviFont { + DviFont *next; + DviFont *prev; + int type; + Int32 checksum; + int hdpi; + int vdpi; + Int32 scale; + Int32 design; + FILE *in; + char *fontname; + char *filename; + int links; + int loc; + int hic; + Uint flags; + DviFontSearch search; + DviFontChar *chars; + DviFontRef *subfonts; + void *private; +}; + +/* + * Dvi context + */ + +typedef enum { + MDVI_ORIENT_TBLR = 0, /* top to bottom, left to right */ + MDVI_ORIENT_TBRL = 1, /* top to bottom, right to left */ + MDVI_ORIENT_BTLR = 2, /* bottom to top, left to right */ + MDVI_ORIENT_BTRL = 3, /* bottom to top, right to left */ + MDVI_ORIENT_RP90 = 4, /* rotated +90 degrees (counter-clockwise) */ + MDVI_ORIENT_RM90 = 5, /* rotated -90 degrees (clockwise) */ + MDVI_ORIENT_IRP90 = 6, /* flip horizontally, then rotate by +90 */ + MDVI_ORIENT_IRM90 = 7 /* rotate by -90, then flip horizontally */ +} DviOrientation; + +typedef enum { + MDVI_PAGE_SORT_UP, /* up, using \counter0 */ + MDVI_PAGE_SORT_DOWN, /* down, using \counter0 */ + MDVI_PAGE_SORT_RANDOM, /* randomly */ + MDVI_PAGE_SORT_DVI_UP, /* up, by location in DVI file */ + MDVI_PAGE_SORT_DVI_DOWN, /* down, by location in DVI file */ + MDVI_PAGE_SORT_NONE /* don't sort */ +} DviPageSort; + +struct _DviParams { + double mag; /* magnification */ + double conv; /* horizontal DVI -> pixel */ + double vconv; /* vertical DVI -> pixel */ + double tfm_conv; /* TFM -> DVI */ + double gamma; /* gamma correction factor */ + Uint dpi; /* horizontal resolution */ + Uint vdpi; /* vertical resolution */ + int hshrink; /* horizontal shrinking factor */ + int vshrink; /* vertical shrinking factor */ + Uint density; /* pixel density */ + Uint flags; /* flags (see MDVI_PARAM macros) */ + int hdrift; /* max. horizontal drift */ + int vdrift; /* max. vertical drift */ + int vsmallsp; /* small vertical space */ + int thinsp; /* small horizontal space */ + int layer; /* visible layer (for layered DVI files) */ + Ulong fg; /* foreground color */ + Ulong bg; /* background color */ + DviOrientation orientation; /* page orientation */ + int base_x; + int base_y; +}; + +typedef enum { + MDVI_PARAM_LAST = 0, + MDVI_SET_DPI = 1, + MDVI_SET_XDPI = 2, + MDVI_SET_YDPI = 3, + MDVI_SET_SHRINK = 4, + MDVI_SET_XSHRINK = 5, + MDVI_SET_YSHRINK = 6, + MDVI_SET_GAMMA = 7, + MDVI_SET_DENSITY = 8, + MDVI_SET_MAGNIFICATION = 9, + MDVI_SET_DRIFT = 10, + MDVI_SET_HDRIFT = 11, + MDVI_SET_VDRIFT = 12, + MDVI_SET_ORIENTATION = 13, + MDVI_SET_FOREGROUND = 14, + MDVI_SET_BACKGROUND = 15 +} DviParamCode; + +struct _DviBuffer { + Uchar *data; + size_t size; /* allocated size */ + size_t length; /* amount of data buffered */ + size_t pos; /* current position in buffer */ + int frozen; /* can we free this data? */ +}; + +/* DVI registers */ +struct _DviState { + int h; + int v; + int hh; + int vv; + int w; + int x; + int y; + int z; +}; + +struct _DviColorPair { + Ulong fg; + Ulong bg; +}; + +struct _DviContext { + char *filename; /* name of the DVI file */ + FILE *in; /* from here we read */ + char *fileid; /* from preamble */ + int npages; /* number of pages */ + int currpage; /* currrent page (0 based) */ + int depth; /* recursion depth */ + DviBuffer buffer; /* input buffer */ + DviParams params; /* parameters */ + DviPaper paper; /* paper type */ + Int32 num; /* numerator */ + Int32 den; /* denominator */ + DviFontRef *fonts; /* fonts used in this file */ + DviFontRef **fontmap; /* for faster id lookups */ + DviFontRef *currfont; /* current font */ + int nfonts; /* # of fonts used in this job */ + Int32 dvimag; /* original magnification */ + double dviconv; /* unshrunk scaling factor */ + double dvivconv; /* unshrunk scaling factor (vertical) */ + int dvi_page_w; /* unscaled page width */ + int dvi_page_h; /* unscaled page height */ + Ulong modtime; /* file modification time */ + PageNum *pagemap; /* page table */ + DviState pos; /* registers */ + DviPageSpec *pagesel; /* page selection data */ + int curr_layer; /* current layer */ + DviState *stack; /* DVI stack */ + int stacksize; /* stack depth */ + int stacktop; /* stack pointer */ + DviDevice device; /* device-specific routines */ + Ulong curr_fg; /* rendering color */ + Ulong curr_bg; + + DviColorPair *color_stack; + int color_top; + int color_size; + + DviFontRef *(*findref) __PROTO((DviContext *, Int32)); + void *user_data; /* client data attached to this context */ +}; + +typedef enum { + MDVI_RANGE_BOUNDED, /* range is finite */ + MDVI_RANGE_LOWER, /* range has a lower bound */ + MDVI_RANGE_UPPER, /* range has an upper bound */ + MDVI_RANGE_UNBOUNDED /* range has no bounds at all */ +} DviRangeType; + +struct _DviRange { + DviRangeType type; /* one of the above */ + int from; /* lower bound */ + int to; /* upper bound */ + int step; /* step */ +}; + + +typedef void (*DviSpecialHandler) + __PROTO((DviContext *dvi, const char *prefix, const char *arg)); + +#define RANGE_HAS_LOWER(x) \ + ((x) == MDVI_RANGE_BOUNDED || (x) == MDVI_RANGE_LOWER) +#define RANGE_HAS_UPPER(x) \ + ((x) == MDVI_RANGE_BOUNDED || (x) == MDVI_RANGE_UPPER) + +/* + * Macros and prototypes + */ + +#define MDVI_PARAM_ANTIALIASED 1 +#define MDVI_PARAM_MONO 2 +#define MDVI_PARAM_CHARBOXES 4 +#define MDVI_PARAM_SHOWUNDEF 8 +#define MDVI_PARAM_DELAYFONTS 16 + +/* + * The FALLBACK priority class is reserved for font formats that + * contain no glyph information and are to be used as a last + * resort (e.g. TFM, AFM) + */ +#define MDVI_FONTPRIO_FALLBACK -3 +#define MDVI_FONTPRIO_LOWEST -2 +#define MDVI_FONTPRIO_LOW -1 +#define MDVI_FONTPRIO_NORMAL 0 +#define MDVI_FONTPRIO_HIGH 1 +#define MDVI_FONTPRIO_HIGHEST 2 + +#define MDVI_FONT_ENCODED (1 << 0) + +#define MDVI_GLYPH_EMPTY ((void *)1) +/* does the glyph have a non-empty bitmap/image? */ +#define MDVI_GLYPH_NONEMPTY(x) ((x) && (x) != MDVI_GLYPH_EMPTY) +/* has the glyph been loaded from disk? */ +#define MDVI_GLYPH_UNSET(x) ((x) == NULL) +/* do we have only a bounding box for this glyph? */ +#define MDVI_GLYPH_ISEMPTY(x) ((x) == MDVI_GLYPH_EMPTY) + +#define MDVI_ENABLED(d,x) ((d)->params.flags & (x)) +#define MDVI_DISABLED(d,x) !MDVI_ENABLED((d), (x)) + +#define MDVI_LASTPAGE(d) ((d)->npages - 1) +#define MDVI_NPAGES(d) (d)->npages +#define MDVI_VALIDPAGE(d,p) ((p) >= 0 && (p) <= MDVI_LASTPAGE(d)) +#define MDVI_FLAGS(d) (d)->params.flags +#define MDVI_SHRINK_FROM_DPI(d) Max(1, (d) / 75) +#define MDVI_CURRFG(d) (d)->curr_fg +#define MDVI_CURRBG(d) (d)->curr_bg + +#define pixel_round(d,v) (int)((d)->params.conv * (v) + 0.5) +#define vpixel_round(d,v) (int)((d)->params.vconv * (v) + 0.5) +#define rule_round(d,v) (int)((d)->params.conv * (v) + 0.99999) /*9999999)*/ +#define vrule_round(d,v) (int)((d)->params.vconv * (v) + 0.99999) + +extern int mdvi_reload __PROTO((DviContext *, DviParams *)); +extern void mdvi_setpage __PROTO((DviContext *, int)); +extern int mdvi_dopage __PROTO((DviContext *, int)); +extern void mdvi_shrink_glyph __PROTO((DviContext *, DviFont *, DviFontChar *, DviGlyph *)); +extern void mdvi_shrink_box __PROTO((DviContext *, DviFont *, DviFontChar *, DviGlyph *)); +extern void mdvi_shrink_glyph_grey __PROTO((DviContext *, DviFont *, DviFontChar *, DviGlyph *)); +extern int mdvi_find_tex_page __PROTO((DviContext *, int)); +extern int mdvi_configure __PROTO((DviContext *, DviParamCode, ...)); + +extern int get_tfm_chars __PROTO((DviParams *, DviFont *, TFMInfo *, int)); +extern int tfm_load_file __PROTO((const char *, TFMInfo *)); +extern int afm_load_file __PROTO((const char *, TFMInfo *)); +extern TFMInfo *get_font_metrics __PROTO((const char *, int, const char *)); +extern char *lookup_font_metrics __PROTO((const char *, int *)); +extern void free_font_metrics __PROTO((TFMInfo *)); +extern void flush_font_metrics __PROTO((void)); + +#define get_metrics(name) get_font_metrics((name), DviFontAny, NULL) + +extern void mdvi_sort_pages __PROTO((DviContext *, DviPageSort)); + +extern void mdvi_init_kpathsea __PROTO((const char *, const char *, const char *, int, const char *)); + +extern DviContext* mdvi_init_context __PROTO((DviParams *, DviPageSpec *, const char *)); +extern void mdvi_destroy_context __PROTO((DviContext *)); + +/* helper macros that call mdvi_configure() */ +#define mdvi_config_one(d,x,y) mdvi_configure((d), (x), (y), MDVI_PARAM_LAST) +#define mdvi_set_dpi(d,x) mdvi_config_one((d), MDVI_SET_DPI, (x)) +#define mdvi_set_xdpi(d,x) mdvi_config_one((d), MDVI_SET_XDPI, (x)) +#define mdvi_set_ydpi(d,x) mdvi_config_one((d), MDVI_SET_YDPI, (x)) +#define mdvi_set_hshrink(d,h) mdvi_config_one((d), MDVI_SET_XSHRINK, (h)) +#define mdvi_set_vshrink(d,h) mdvi_config_one((d), MDVI_SET_YSHRINK, (h)) +#define mdvi_set_gamma(d,g) mdvi_config_one((d), MDVI_SET_GAMMA, (g)) +#define mdvi_set_density(d,x) mdvi_config_one((d), MDVI_SET_DENSITY, (x)) +#define mdvi_set_drift(d,x) mdvi_config_one((d), MDVI_SET_DRIFT, (x)) +#define mdvi_set_hdrift(d,h) mdvi_config_one((d), MDVI_SET_HDRIFT, (h)) +#define mdvi_set_vdrift(d,v) mdvi_config_one((d), MDVI_SET_VDRIFT, (v)) +#define mdvi_set_mag(d,m) \ + mdvi_config_one((d), MDVI_SET_MAGNIFICATION, (m)) +#define mdvi_set_foreground(d,x) \ + mdvi_config_one((d), MDVI_SET_FOREGROUND, (x)) +#define mdvi_set_background(d,x) \ + mdvi_config_one((d), MDVI_SET_BACKGROUND, (x)) +#define mdvi_set_orientation(d,x) \ + mdvi_config_one((d), MDVI_SET_ORIENTATION, (x)) +#define mdvi_set_shrink(d,h,v) \ + mdvi_configure((d), MDVI_SET_XSHRINK, (h), \ + MDVI_SET_YSHRINK, (v), MDVI_PARAM_LAST) + +extern DviRange* mdvi_parse_range __PROTO((const char *, DviRange *, int *, char **)); +extern DviPageSpec* mdvi_parse_page_spec __PROTO((const char *)); +extern void mdvi_free_page_spec __PROTO((DviPageSpec *)); +extern int mdvi_in_range __PROTO((DviRange *, int, int)); +extern int mdvi_range_length __PROTO((DviRange *, int)); +extern int mdvi_page_selected __PROTO((DviPageSpec *, PageNum, int)); + +/* Specials */ +extern int mdvi_register_special __PROTO(( + const char *label, + const char *prefix, + const char *regex, + DviSpecialHandler handler, + int replace)); +extern int mdvi_unregister_special __PROTO((const char *prefix)); +extern int mdvi_do_special __PROTO((DviContext *dvi, char *dvi_special)); +extern void mdvi_flush_specials __PROTO((void)); + +/* Fonts */ + +#define MDVI_FONTSEL_BITMAP (1 << 0) +#define MDVI_FONTSEL_GREY (1 << 1) +#define MDVI_FONTSEL_GLYPH (1 << 2) + +#define FONTCHAR(font, code) \ + (((code) < font->loc || (code) > font->hic || !(font)->chars) ? \ + NULL : &font->chars[(code) - (font)->loc]) +#define FONT_GLYPH_COUNT(font) ((font)->hic - (font)->loc + 1) + +#define glyph_present(x) ((x) && (x)->offset) + +/* create a reference to a font */ +extern DviFontRef *font_reference __PROTO((DviParams *params, + Int32 dvi_id, + const char *font_name, + Int32 checksum, + int xdpi, + int ydpi, + Int32 scale_factor)); + +/* drop a reference to a font */ +extern void font_drop_one __PROTO((DviFontRef *)); + +/* drop a chain of references */ +extern void font_drop_chain __PROTO((DviFontRef *)); + +/* destroy selected information for a glyph */ +extern void font_reset_one_glyph __PROTO((DviDevice *, DviFontChar *, int)); + +/* destroy selected information for all glyphs in a font */ +extern void font_reset_font_glyphs __PROTO((DviDevice *, DviFont *, int)); + +/* same for a chain of font references */ +extern void font_reset_chain_glyphs __PROTO((DviDevice *, DviFontRef *, int)); + +extern void font_finish_definitions __PROTO((DviContext *)); + +/* lookup an id # in a reference chain */ +extern DviFontRef* font_find_flat __PROTO((DviContext *, Int32)); +extern DviFontRef* font_find_mapped __PROTO((DviContext *, Int32)); + +/* called to reopen (or rewind) a font file */ +extern int font_reopen __PROTO((DviFont *)); + +/* reads a glyph from a font, and makes all necessary transformations */ +extern DviFontChar* font_get_glyph __PROTO((DviContext *, DviFont *, int)); + +/* transform a glyph according to the given orientation */ +extern void font_transform_glyph __PROTO((DviOrientation, DviGlyph *)); + +/* destroy all fonts that are not being used, returns number of fonts freed */ +extern int font_free_unused __PROTO((DviDevice *)); + +#define font_free_glyph(dev, font, code) \ + font_reset_one_glyph((dev), \ + FONTCHAR((font), (code)), MDVI_FONTSEL_GLYPH) + +extern int mdvi_encode_font __PROTO((DviParams *, DviFont *)); + + +/* font lookup functions */ +extern int mdvi_register_font_type __PROTO((DviFontInfo *, int)); +extern char **mdvi_list_font_class __PROTO((int)); +extern int mdvi_get_font_classes __PROTO((void)); +extern int mdvi_unregister_font_type __PROTO((const char *, int)); +extern char *mdvi_lookup_font __PROTO((DviFontSearch *)); +extern DviFont *mdvi_add_font __PROTO((const char *, Int32, int, int, Int32)); +extern int mdvi_font_retry __PROTO((DviParams *, DviFont *)); + +/* Miscellaneous */ + +extern int mdvi_set_logfile __PROTO((const char *)); +extern int mdvi_set_logstream __PROTO((FILE *)); +extern int mdvi_set_loglevel __PROTO((int)); + +#define mdvi_stop_logging(x) mdvi_set_logstream(NULL) + +/* this will check the environment and then `texmf.cnf' for + * the given name changed to lowercase, and `_' changed to `-' */ +extern char* mdvi_getenv __PROTO((const char *)); + +#endif /* _MDVI_DVI_H */ diff --git a/backend/dvi/mdvi-lib/pagesel.c b/backend/dvi/mdvi-lib/pagesel.c new file mode 100644 index 00000000..5a153955 --- /dev/null +++ b/backend/dvi/mdvi-lib/pagesel.c @@ -0,0 +1,491 @@ +/* pagesel.c -- Page selection mechanism */ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +#include "mdvi.h" +#include "private.h" + +char *program_name = "page"; + +struct _DviPageSpec { + DviRange *ranges; + int nranges; +}; + +DviRange *mdvi_parse_range(const char *format, DviRange *limit, int *nitems, char **endptr) +{ + int quoted; + int size; + int curr; + int done; + int lower; + int upper; + int type; + char * cp; + char * copy; + char * text; + DviRange one; + DviRange *range; + + quoted = (*format == '{'); + if(quoted) format++; + + size = 0; + curr = 0; + range = NULL; + copy = mdvi_strdup(format); + done = 0; + lower = 0; + upper = 0; + type = MDVI_RANGE_UNBOUNDED; + + if(limit) { + switch(limit->type) { + case MDVI_RANGE_BOUNDED: + lower = limit->from; + upper = limit->to; + break; + case MDVI_RANGE_UPPER: + lower = INT_MIN; + upper = limit->to; + break; + case MDVI_RANGE_LOWER: + lower = limit->from; + upper = INT_MAX; + break; + case MDVI_RANGE_UNBOUNDED: + lower = INT_MIN; + upper = INT_MAX; + break; + } + type = limit->type; + } else { + lower = INT_MIN; + upper = INT_MAX; + type = MDVI_RANGE_UNBOUNDED; + } + one.type = type; + one.from = lower; + one.to = upper; + one.step = 1; + for(cp = text = copy; !done; cp++) { + char *p; + int f, t, s; + int ch; + int this_type; + int lower_given = 0; + int upper_given = 0; + + if(*cp == 0 || *cp == '.' || (*cp == '}' && quoted)) + done = 1; + else if(*cp != ',') + continue; + + if(text == cp) + continue; + ch = *cp; + *cp = 0; + f = lower; + t = upper; + s = 1; + + p = strchr(text, ':'); + if(p) *p++ = 0; + if(*text) { + lower_given = 1; + f = strtol(text, NULL, 0); + } + if(p == NULL) { + if(lower_given) { + upper_given = 1; + t = f; s = 1; + } + goto finish; + } + text = p; + p = strchr(text, ':'); + if(p) *p++ = 0; + if(*text) { + upper_given = 1; + t = strtol(text, NULL, 0); + } + if(p == NULL) + goto finish; + text = p; + if(*text) + s = strtol(text, NULL, 0); +finish: + if(lower_given && upper_given) + this_type = MDVI_RANGE_BOUNDED; + else if(lower_given) { + if(!RANGE_HAS_UPPER(type)) + this_type = MDVI_RANGE_LOWER; + else + this_type = MDVI_RANGE_BOUNDED; + t = upper; + } else if(upper_given) { + if(RANGE_HAS_UPPER(one.type)) { + one.to++; + this_type = MDVI_RANGE_BOUNDED; + } else { + one.to = lower; + if(!RANGE_HAS_LOWER(type)) + this_type = MDVI_RANGE_UPPER; + else + this_type = MDVI_RANGE_BOUNDED; + } + f = one.to; + } else { + this_type = type; + f = lower; + t = upper; + } + one.type = this_type; + one.to = t; + one.from = f; + one.step = s; + + if(curr == size) { + size += 8; + range = mdvi_realloc(range, size * sizeof(DviRange)); + } + memcpy(&range[curr++], &one, sizeof(DviRange)); + *cp = ch; + text = cp + 1; + } + if(done) + cp--; + if(quoted && *cp == '}') + cp++; + if(endptr) + *endptr = (char *)format + (cp - copy); + if(curr && curr < size) + range = mdvi_realloc(range, curr * sizeof(DviRange)); + *nitems = curr; + mdvi_free(copy); + return range; +} + +DviPageSpec *mdvi_parse_page_spec(const char *format) +{ + /* + * a page specification looks like this: + * '{'RANGE_SPEC'}' for a DVI spec + * '{'RANGE_SPEC'}' '.' ... for a TeX spec + */ + DviPageSpec *spec; + DviRange *range; + int count; + int i; + char *ptr; + + spec = xnalloc(struct _DviPageSpec *, 11); + for(i = 0; i < 11; i++) + spec[i] = NULL; + + /* check what kind of spec we're parsing */ + if(*format != '*') { + range = mdvi_parse_range(format, NULL, &count, &ptr); + if(ptr == format) { + if(range) mdvi_free(range); + mdvi_error(_("invalid page specification `%s'\n"), format); + return NULL; + } + } else + range = NULL; + + if(*format == 'D' || *format == 'd' || *ptr != '.') + i = 0; + else + i = 1; + + if(range) { + spec[i] = xalloc(struct _DviPageSpec); + spec[i]->ranges = range; + spec[i]->nranges = count; + } else + spec[i] = NULL; + + if(*ptr != '.') { + if(*ptr) + mdvi_warning(_("garbage after DVI page specification ignored\n")); + return spec; + } + + for(i++; *ptr == '.' && i <= 10; i++) { + ptr++; + if(*ptr == '*') { + ptr++; + range = NULL; + } else { + char *end; + + range = mdvi_parse_range(ptr, NULL, &count, &end); + if(end == ptr) { + if(range) mdvi_free(range); + range = NULL; + } else + ptr = end; + } + if(range != NULL) { + spec[i] = xalloc(struct _DviPageSpec); + spec[i]->ranges = range; + spec[i]->nranges = count; + } else + spec[i] = NULL; + } + + if(i > 10) + mdvi_warning(_("more than 10 counters in page specification\n")); + else if(*ptr) + mdvi_warning(_("garbage after TeX page specification ignored\n")); + + return spec; +} + +/* returns non-zero if the given page is included by `spec' */ +int mdvi_page_selected(DviPageSpec *spec, PageNum page, int dvipage) +{ + int i; + int not_found; + + if(spec == NULL) + return 1; + if(spec[0]) { + not_found = mdvi_in_range(spec[0]->ranges, + spec[0]->nranges, dvipage); + if(not_found < 0) + return 0; + } + for(i = 1; i <= 10; i++) { + if(spec[i] == NULL) + continue; + not_found = mdvi_in_range(spec[i]->ranges, + spec[i]->nranges, (int)page[i]); + if(not_found < 0) + return 0; + } + return 1; +} + +void mdvi_free_page_spec(DviPageSpec *spec) +{ + int i; + + for(i = 0; i < 11; i++) + if(spec[i]) { + mdvi_free(spec[i]->ranges); + mdvi_free(spec[i]); + } + mdvi_free(spec); +} + +int mdvi_in_range(DviRange *range, int nitems, int value) +{ + DviRange *r; + + for(r = range; r < range + nitems; r++) { + int cond; + + switch(r->type) { + case MDVI_RANGE_BOUNDED: + if(value == r->from) + return (r - range); + if(r->step < 0) + cond = (value <= r->from) && (value >= r->to); + else + cond = (value <= r->to) && (value >= r->from); + if(cond && ((value - r->from) % r->step) == 0) + return (r - range); + break; + case MDVI_RANGE_LOWER: + if(value == r->from) + return (r - range); + if(r->step < 0) + cond = (value < r->from); + else + cond = (value > r->from); + if(cond && ((value - r->from) % r->step) == 0) + return (r - range); + break; + case MDVI_RANGE_UPPER: + if(value == r->to) + return (r - range); + if(r->step < 0) + cond = (value > r->to); + else + cond = (value < r->to); + if(cond && ((value - r->to) % r->step) == 0) + return (r - range); + break; + case MDVI_RANGE_UNBOUNDED: + if((value % r->step) == 0) + return (r - range); + break; + } + } + return -1; +} + +int mdvi_range_length(DviRange *range, int nitems) +{ + int count = 0; + DviRange *r; + + for(r = range; r < range + nitems; r++) { + int n; + + if(r->type != MDVI_RANGE_BOUNDED) + return -2; + n = (r->to - r->from) / r->step; + if(n < 0) + n = 0; + count += n + 1; + } + return count; +} + +#ifdef TEST + +void print_range(DviRange *range) +{ + switch(range->type) { + case MDVI_RANGE_BOUNDED: + printf("From %d to %d, step %d\n", + range->from, range->to, range->step); + break; + case MDVI_RANGE_LOWER: + printf("From %d, step %d\n", + range->from, range->step); + break; + case MDVI_RANGE_UPPER: + printf("From %d, step -%d\n", + range->to, range->step); + break; + case MDVI_RANGE_UNBOUNDED: + printf("From 0, step %d and %d\n", + range->step, -range->step); + break; + } +} + +int main() +{ +#if 0 + char buf[256]; + DviRange limit; + + limit.from = 0; + limit.to = 100; + limit.step = 2; + limit.type = MDVI_RANGE_UNBOUNDED; + while(1) { + DviRange *range; + char *end; + int count; + int i; + + printf("Range> "); fflush(stdout); + if(fgets(buf, 256, stdin) == NULL) + break; + if(buf[strlen(buf)-1] == '\n') + buf[strlen(buf)-1] = 0; + if(buf[0] == 0) + continue; + end = NULL; + range = mdvi_parse_range(buf, &limit, &count, &end); + if(range == NULL) { + printf("range is empty\n"); + continue; + } + + for(i = 0; i < count; i++) { + printf("Range %d (%d elements):\n", + i, mdvi_range_length(&range[i], 1)); + print_range(&range[i]); + } + if(end && *end) + printf("Tail: [%s]\n", end); + printf("range has %d elements\n", + mdvi_range_length(range, count)); +#if 1 + while(1) { + int v; + + printf("Value: "); fflush(stdout); + if(fgets(buf, 256, stdin) == NULL) + break; + if(buf[strlen(buf)-1] == '\n') + buf[strlen(buf)-1] = 0; + if(buf[0] == 0) + break; + v = atoi(buf); + i = mdvi_in_range(range, count, v); + if(i == -1) + printf("%d not in range\n", v); + else { + printf("%d in range: ", v); + print_range(&range[i]); + } + } +#endif + if(range) mdvi_free(range); + } +#else + DviPageSpec *spec; + char buf[256]; + + while(1) { + printf("Spec> "); fflush(stdout); + if(fgets(buf, 256, stdin) == NULL) + break; + if(buf[strlen(buf)-1] == '\n') + buf[strlen(buf)-1] = 0; + if(buf[0] == 0) + continue; + spec = mdvi_parse_page_spec(buf); + if(spec == NULL) + printf("no spec parsed\n"); + else { + int i; + + printf("spec = "); + for(i = 0; i < 11; i++) { + printf("Counter %d:\n", i); + if(spec[i]) { + int k; + + for(k = 0; k < spec[i]->nranges; k++) + print_range(&spec[i]->ranges[k]); + } else + printf("\t*\n"); + } + mdvi_free_page_spec(spec); + } + } +#endif + exit(0); + +} +#endif /* TEST */ diff --git a/backend/dvi/mdvi-lib/paper.c b/backend/dvi/mdvi-lib/paper.c new file mode 100644 index 00000000..42cbcd3f --- /dev/null +++ b/backend/dvi/mdvi-lib/paper.c @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include "common.h" +#include "mdvi.h" +#include "private.h" + +static const DviPaperSpec papers[] = { + {"ISO", 0, 0}, + {"4A0", "1682mm", "2378mm"}, + {"2A0", "1189mm", "1682mm"}, + {"A0", "841mm", "1189mm"}, + {"A1", "594mm", "841mm"}, + {"A2", "420mm", "594mm"}, + {"A3", "297mm", "420mm"}, + {"A4", "210mm", "297mm"}, + {"A5", "148mm", "210mm"}, + {"A6", "105mm", "148mm"}, + {"A7", "74mm", "105mm"}, + {"A8", "52mm", "74mm"}, + {"A9", "37mm", "52mm"}, + {"A10", "26mm", "37mm"}, + {"B0", "1000mm", "1414mm"}, + {"B1", "707mm", "1000mm"}, + {"B2", "500mm", "707mm"}, + {"B3", "353mm", "500mm"}, + {"B4", "250mm", "353mm"}, + {"B5", "176mm", "250mm"}, + {"B6", "125mm", "176mm"}, + {"B7", "88mm", "125mm"}, + {"B8", "62mm", "88mm"}, + {"B9", "44mm", "62mm"}, + {"B10", "31mm", "44mm"}, + {"C0", "917mm", "1297mm"}, + {"C1", "648mm", "917mm"}, + {"C2", "458mm", "648mm"}, + {"C3", "324mm", "458mm"}, + {"C4", "229mm", "324mm"}, + {"C5", "162mm", "229mm"}, + {"C6", "114mm", "162mm"}, + {"C7", "81mm", "114mm"}, + {"C8", "57mm", "81mm"}, + {"C9", "40mm", "57mm"}, + {"C10", "28mm", "40mm"}, + {"US", 0, 0}, + {"archA", "9in", "12in"}, + {"archB", "12in", "18in"}, + {"archC", "18in", "24in"}, + {"archD", "24in", "36in"}, + {"archE", "36in", "48in"}, + {"executive", "7.5in", "10in"}, + {"flsa", "8.5in", "13in"}, + {"flse", "8.5in", "13in"}, + {"halfletter", "5.5in", "8.5in"}, + {"letter", "8.5in", "11in"}, + {"legal", "8.5in", "14in"}, + {"ledger", "17in", "11in"}, + {"note", "7.5in", "10in"}, + {"tabloid", "11in", "17in"}, + {"statement", "5.5in", "8.5in"}, + {0, 0, 0} +}; + +static DviPaperClass str2class(const char *name) +{ + if(STRCEQ(name, "ISO")) + return MDVI_PAPER_CLASS_ISO; + else if(STRCEQ(name, "US")) + return MDVI_PAPER_CLASS_US; + return MDVI_PAPER_CLASS_CUSTOM; +} + +int mdvi_get_paper_size(const char *name, DviPaper *paper) +{ + const DviPaperSpec *sp; + double a, b; + char c, d, e, f; + char buf[32]; + + paper->pclass = MDVI_PAPER_CLASS_CUSTOM; + if(sscanf(name, "%lfx%lf%c%c", &a, &b, &c, &d) == 4) { + sprintf(buf, "%12.16f%c%c", a, c, d); + paper->inches_wide = unit2pix_factor(buf); + sprintf(buf, "%12.16f%c%c", b, c, d); + paper->inches_tall = unit2pix_factor(buf); + paper->name = _("custom"); + return 0; + } else if(sscanf(name, "%lf%c%c,%lf%c%c", &a, &c, &d, &b, &e, &f) == 6) { + sprintf(buf, "%12.16f%c%c", a, c, d); + paper->inches_wide = unit2pix_factor(buf); + sprintf(buf, "%12.16f%c%c", b, e, f); + paper->inches_tall = unit2pix_factor(buf); + paper->name = _("custom"); + return 0; + } + + for(sp = &papers[0]; sp->name; sp++) { + if(!sp->width || !sp->height) { + paper->pclass = str2class(sp->name); + continue; + } + if(strcasecmp(sp->name, name) == 0) { + paper->inches_wide = unit2pix_factor(sp->width); + paper->inches_tall = unit2pix_factor(sp->height); + paper->name = sp->name; + return 0; + } + } + return -1; +} + +DviPaperSpec *mdvi_get_paper_specs(DviPaperClass pclass) +{ + int i; + int first, count; + DviPaperSpec *spec, *ptr; + + first = -1; + count = 0; + if(pclass == MDVI_PAPER_CLASS_ANY || + pclass == MDVI_PAPER_CLASS_CUSTOM) { + first = 0; + count = (sizeof(papers) / sizeof(papers[0])) - 3; + } else for(i = 0; papers[i].name; i++) { + if(papers[i].width == NULL) { + if(str2class(papers[i].name) == pclass) + first = i; + else if(first >= 0) + break; + } else if(first >= 0) + count++; + } + ptr = spec = xnalloc(DviPaperSpec, count + 1); + for(i = first; papers[i].name&& count > 0; i++) { + if(papers[i].width) { + ptr->name = papers[i].name; + ptr->width = papers[i].width; + ptr->height = papers[i].height; + ptr++; + count--; + } + } + ptr->name = NULL; + ptr->width = NULL; + ptr->height = NULL; + + return spec; +} + +void mdvi_free_paper_specs(DviPaperSpec *spec) +{ + mdvi_free(spec); +} diff --git a/backend/dvi/mdvi-lib/paper.h b/backend/dvi/mdvi-lib/paper.h new file mode 100644 index 00000000..d42ee079 --- /dev/null +++ b/backend/dvi/mdvi-lib/paper.h @@ -0,0 +1,32 @@ +#ifndef MDVI_PAPER +#define MDVI_PAPER + +typedef struct _DviPaper DviPaper; +typedef struct _DviPaperSpec DviPaperSpec; + +typedef enum { + MDVI_PAPER_CLASS_ISO, + MDVI_PAPER_CLASS_US, + MDVI_PAPER_CLASS_ANY, + MDVI_PAPER_CLASS_CUSTOM +} DviPaperClass; + +struct _DviPaper { + DviPaperClass pclass; + const char *name; + double inches_wide; + double inches_tall; +}; + +struct _DviPaperSpec { + const char *name; + const char *width; + const char *height; +}; + + +extern int mdvi_get_paper_size __PROTO((const char *, DviPaper *)); +extern DviPaperSpec* mdvi_get_paper_specs __PROTO((DviPaperClass)); +extern void mdvi_free_paper_specs __PROTO((DviPaperSpec *)); + +#endif diff --git a/backend/dvi/mdvi-lib/pk.c b/backend/dvi/mdvi-lib/pk.c new file mode 100644 index 00000000..a5791869 --- /dev/null +++ b/backend/dvi/mdvi-lib/pk.c @@ -0,0 +1,570 @@ + +/* Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * History: + * + * 11/3/2000: + * - First working version + * 11/4/2000: + * - FIXED: entirely white/black rows were missed. + * 11/8/2000: + * - TESTED: Glyphs are rendered correctly in different byte orders. + * - Made bitmap code much more efficient and compact. + */ + +#include +#include +#include +#include +#include +#include + +#include "mdvi.h" +#include "private.h" + +#define PK_ID 89 +#define PK_CMD_START 240 +#define PK_X1 240 +#define PK_X2 241 +#define PK_X3 242 +#define PK_X4 243 +#define PK_Y 244 +#define PK_POST 245 +#define PK_NOOP 246 +#define PK_PRE 247 + +#define PK_DYN_F(x) (((x) >> 4) & 0xf) +#define PK_PACKED(x) (PK_DYN_F(x) != 14) + +static int pk_load_font __PROTO((DviParams *, DviFont *)); +static int pk_font_get_glyph __PROTO((DviParams *, DviFont *, int)); + +static int pk_auto_generate = 1; /* this is ON by default */ + +typedef struct { + char currbyte; + char nybpos; + int dyn_f; +} pkread; + +static char *pk_lookup __PROTO((const char *, Ushort *, Ushort *)); +static char *pk_lookupn __PROTO((const char *, Ushort *, Ushort *)); + +/* only symbols exported by this file */ +DviFontInfo pk_font_info = { + "PK", + 0, /* scaling not supported natively */ + pk_load_font, + pk_font_get_glyph, + mdvi_shrink_glyph, + mdvi_shrink_glyph_grey, + NULL, /* free */ + NULL, /* reset */ + pk_lookup, /* lookup */ + kpse_pk_format, + NULL +}; + +DviFontInfo pkn_font_info = { + "PKN", + 0, /* scaling not supported natively */ + pk_load_font, + pk_font_get_glyph, + mdvi_shrink_glyph, + mdvi_shrink_glyph_grey, + NULL, /* free */ + NULL, /* reset */ + pk_lookupn, /* lookup */ + kpse_pk_format, + NULL +}; + +static char *pk_lookup(const char *name, Ushort *hdpi, Ushort *vdpi) +{ + kpse_glyph_file_type type; + char *filename; + + if(pk_auto_generate == 0) { + kpse_set_program_enabled(kpse_pk_format, 1, kpse_src_cmdline); + pk_auto_generate = 1; + } + filename = kpse_find_glyph(name, Max(*hdpi, *vdpi), + kpse_pk_format, &type); + if(filename && type.source == kpse_glyph_source_fallback) { + mdvi_free(filename); + filename = NULL; + } else if(filename) { + *hdpi = *vdpi = type.dpi; + } + return filename; +} + +static char *pk_lookupn(const char *name, Ushort *hdpi, Ushort *vdpi) +{ + kpse_glyph_file_type type; + char *filename; + + if(pk_auto_generate) { + kpse_set_program_enabled(kpse_pk_format, 0, kpse_src_cmdline); + pk_auto_generate = 0; + } + filename = kpse_find_glyph(name, Max(*hdpi, *vdpi), + kpse_pk_format, &type); + if(filename && type.source == kpse_glyph_source_fallback) { + mdvi_free(filename); + filename = NULL; + } else if(filename) { + *hdpi = *vdpi = type.dpi; + } + return filename; +} + +static inline int pk_get_nyb(FILE *p, pkread *pk) +{ + unsigned t; + int nb; + char c; + + t = c = pk->currbyte; + nb = pk->nybpos; + + switch(nb) { + case 0: + c = pk->currbyte = fuget1(p); + t = (c >> 4); + break; + case 1: + t = c; + break; + } + pk->nybpos = !nb; + return (t & 0xf); +} + +/* + * this is a bit cumbersome because we have to pass around + * the `pkread' data... + */ +static int pk_packed_num(FILE *p, pkread *pkr, int *repeat) +{ + int i, j; + int dyn_f = pkr->dyn_f; + + i = pk_get_nyb(p, pkr); + if(i == 0) { + do { + j = pk_get_nyb(p, pkr); + i++; + } while(j == 0); + while(i-- > 0) + j = (j << 4) + pk_get_nyb(p, pkr); + return (j - 15 + ((13 - dyn_f) << 4) + + dyn_f); + } else if(i <= dyn_f) + return i; + else if(i < 14) + return ((i - dyn_f - 1) << 4) + + pk_get_nyb(p, pkr) + dyn_f + 1; + else { + *repeat = 1; + if(i == 14) + *repeat = pk_packed_num(p, pkr, repeat); + return pk_packed_num(p, pkr, repeat); + } +} + +#define ROUND(x,y) (((x) + (y) - 1) / (y)) + +static BITMAP *get_bitmap(FILE *p, int w, int h, int flags) +{ + int i, j; + BmUnit *ptr; + BITMAP *bm; + int bitpos; + int currch; + + flags = 0; /* shut up that compiler */ + bitpos = -1; + if((bm = bitmap_alloc(w, h)) == NULL) + return NULL; + DEBUG((DBG_BITMAPS, "get_bitmap(%d,%d,%d): reading raw bitmap\n", + w, h, flags)); + ptr = bm->data; + currch = 0; + for(i = 0; i < h; i++) { + BmUnit mask; + + mask = FIRSTMASK; + for(j = 0; j < w; j++) { + if(bitpos < 0) { + currch = fuget1(p); + bitpos = 7; + } + if(currch & (1 << bitpos)) + *ptr |= mask; + bitpos--; + if(mask == LASTMASK) { + ptr++; + mask = FIRSTMASK; + } else + NEXTMASK(mask); + } + ptr = bm_offset(ptr, bm->stride); + } + return bm; +} + +static BITMAP *get_packed(FILE *p, int w, int h, int flags) +{ + int inrow, count; + int row; + BITMAP *bm; + int repeat_count; + int paint; + pkread pkr; + + pkr.nybpos = 0; + pkr.currbyte = 0; + pkr.dyn_f = PK_DYN_F(flags); + paint = !!(flags & 0x8); + + repeat_count = 0; + row = 0; + inrow = w; + if((bm = bitmap_alloc(w, h)) == NULL) + return NULL; + DEBUG((DBG_BITMAPS, "get_packed(%d,%d,%d): reading packed glyph\n", + w, h, flags)); + while(row < h) { + int i = 0; + + count = pk_packed_num(p, &pkr, &i); + if(i > 0) { + if(repeat_count) + fprintf(stderr, "second repeat count for this row (had %d and got %d)\n", + repeat_count, i); + repeat_count = i; + } + + if(count >= inrow) { + Uchar *r, *t; + BmUnit *a, mask; + + /* first finish current row */ + if(paint) + bitmap_set_row(bm, row, w - inrow, inrow, paint); + /* now copy it as many times as required */ + r = (Uchar *)bm->data + row * bm->stride; + while(repeat_count-- > 0) { + t = r + bm->stride; + /* copy entire lines */ + memcpy(t, r, bm->stride); + r = t; + row++; + } + repeat_count = 0; + /* count first row we drew */ + row++; + /* update run count */ + count -= inrow; + /* now r points to the beginning of the last row we finished */ + if(paint) + mask = ~((BmUnit)0); + else + mask = 0; + /* goto next row */ + a = (BmUnit *)(r + bm->stride); + /* deal with entirely with/black rows */ + while(count >= w) { + /* count number of atoms in a row */ + i = ROUND(w, BITMAP_BITS); + while(i-- > 0) + *a++ = mask; + count -= w; + row++; + } + inrow = w; + } + if(count > 0) + bitmap_set_row(bm, row, w - inrow, count, paint); + inrow -= count; + paint = !paint; + } + if(row != h || inrow != w) { + mdvi_error(_("Bad PK file: More bits than required\n")); + bitmap_destroy(bm); + return NULL; + } + return bm; +} + +static BITMAP *get_char(FILE *p, int w, int h, int flags) +{ + /* check if dyn_f == 14 */ + if(((flags >> 4) & 0xf) == 14) + return get_bitmap(p, w, h, flags); + else + return get_packed(p, w, h, flags); +} + +/* supports any number of characters in a font */ +static int pk_load_font(DviParams *unused, DviFont *font) +{ + int i; + int flag_byte; + int loc, hic, maxch; + Int32 checksum; + FILE *p; +#ifndef NODEBUG + char s[256]; +#endif + long alpha, beta, z; + + font->chars = xnalloc(DviFontChar, 256); + p = font->in; + memzero(font->chars, 256 * sizeof(DviFontChar)); + for(i = 0; i < 256; i++) + font->chars[i].offset = 0; + + /* check the preamble */ + loc = fuget1(p); hic = fuget1(p); + if(loc != PK_PRE || hic != PK_ID) + goto badpk; + i = fuget1(p); +#ifndef NODEBUG + for(loc = 0; loc < i; loc++) + s[loc] = fuget1(p); + s[loc] = 0; + DEBUG((DBG_FONTS, "(pk) %s: %s\n", font->fontname, s)); +#else + fseek(in, (long)i, SEEK_CUR); +#endif + /* get the design size */ + font->design = fuget4(p); + /* get the checksum */ + checksum = fuget4(p); + if(checksum && font->checksum && font->checksum != checksum) { + mdvi_warning(_("%s: checksum mismatch (expected %u, got %u)\n"), + font->fontname, font->checksum, checksum); + } else if(!font->checksum) + font->checksum = checksum; + /* skip pixel per point ratios */ + fuget4(p); + fuget4(p); + if(feof(p)) + goto badpk; + + /* now start reading the font */ + loc = 256; hic = -1; maxch = 256; + + /* initialize alpha and beta for TFM width computation */ + TFMPREPARE(font->scale, z, alpha, beta); + + while((flag_byte = fuget1(p)) != PK_POST) { + if(feof(p)) + break; + if(flag_byte >= PK_CMD_START) { + switch(flag_byte) { + case PK_X1: + case PK_X2: + case PK_X3: + case PK_X4: { +#ifndef NODEBUG + char *t; + int n; + + i = fugetn(p, flag_byte - PK_X1 + 1); + if(i < 256) + t = &s[0]; + else + t = mdvi_malloc(i + 1); + for(n = 0; n < i; n++) + t[n] = fuget1(p); + t[n] = 0; + DEBUG((DBG_SPECIAL, "(pk) %s: Special \"%s\"\n", + font->fontname, t)); + if(t != &s[0]) + mdvi_free(t); +#else + i = fugetn(p, flag_byte - PK_X1 + 1); + while(i-- > 0) + fuget1(p); +#endif + break; + } + case PK_Y: + i = fuget4(p); + DEBUG((DBG_SPECIAL, "(pk) %s: MF special %u\n", + font->fontname, (unsigned)i)); + break; + case PK_POST: + case PK_NOOP: + break; + case PK_PRE: + mdvi_error(_("%s: unexpected preamble\n"), font->fontname); + goto error; + } + } else { + int pl; + int cc; + int w, h; + int x, y; + int offset; + long tfm; + + switch(flag_byte & 0x7) { + case 7: + pl = fuget4(p); + cc = fuget4(p); + offset = ftell(p) + pl; + tfm = fuget4(p); + fsget4(p); /* skip dx */ + fsget4(p); /* skip dy */ + w = fuget4(p); + h = fuget4(p); + x = fsget4(p); + y = fsget4(p); + break; + case 4: + case 5: + case 6: + pl = (flag_byte % 4) * 65536 + fuget2(p); + cc = fuget1(p); + offset = ftell(p) + pl; + tfm = fuget3(p); + fsget2(p); /* skip dx */ + /* dy assumed 0 */ + w = fuget2(p); + h = fuget2(p); + x = fsget2(p); + y = fsget2(p); + break; + default: + pl = (flag_byte % 4) * 256 + fuget1(p); + cc = fuget1(p); + offset = ftell(p) + pl; + tfm = fuget3(p); + fsget1(p); /* skip dx */ + /* dy assumed 0 */ + w = fuget1(p); + h = fuget1(p); + x = fsget1(p); + y = fsget1(p); + } + if(feof(p)) + break; + if(cc < loc) + loc = cc; + if(cc > hic) + hic = cc; + if(cc > maxch) { + font->chars = xresize(font->chars, + DviFontChar, cc + 16); + for(i = maxch; i < cc + 16; i++) + font->chars[i].offset = 0; + maxch = cc + 16; + } + font->chars[cc].code = cc; + font->chars[cc].flags = flag_byte; + font->chars[cc].offset = ftell(p); + font->chars[cc].width = w; + font->chars[cc].height = h; + font->chars[cc].glyph.data = NULL; + font->chars[cc].x = x; + font->chars[cc].y = y; + font->chars[cc].glyph.x = x; + font->chars[cc].glyph.y = y; + font->chars[cc].glyph.w = w; + font->chars[cc].glyph.h = h; + font->chars[cc].grey.data = NULL; + font->chars[cc].shrunk.data = NULL; + font->chars[cc].tfmwidth = TFMSCALE(z, tfm, alpha, beta); + font->chars[cc].loaded = 0; + fseek(p, (long)offset, SEEK_SET); + } + } + if(flag_byte != PK_POST) { + mdvi_error(_("%s: unexpected end of file (no postamble)\n"), + font->fontname); + goto error; + } + while((flag_byte = fuget1(p)) != EOF) { + if(flag_byte != PK_NOOP) { + mdvi_error(_("invalid PK file! (junk in postamble)\n")); + goto error; + } + } + + /* resize font char data */ + if(loc > 0 || hic < maxch-1) { + memmove(font->chars, font->chars + loc, + (hic - loc + 1) * sizeof(DviFontChar)); + font->chars = xresize(font->chars, + DviFontChar, hic - loc + 1); + } + font->loc = loc; + font->hic = hic; + return 0; + +badpk: + mdvi_error(_("%s: File corrupted, or not a PK file\n"), font->fontname); +error: + mdvi_free(font->chars); + font->chars = NULL; + font->loc = font->hic = 0; + return -1; +} + +static int pk_font_get_glyph(DviParams *params, DviFont *font, int code) +{ + DviFontChar *ch; + + if((ch = FONTCHAR(font, code)) == NULL) + return -1; + + if(ch->offset == 0) + return -1; + DEBUG((DBG_GLYPHS, "(pk) loading glyph for character %d (%dx%d) in font `%s'\n", + code, ch->width, ch->height, font->fontname)); + if(font->in == NULL && font_reopen(font) < 0) + return -1; + if(!ch->width || !ch->height) { + /* this happens for ` ' (ASCII 32) in some fonts */ + ch->glyph.x = ch->x; + ch->glyph.y = ch->y; + ch->glyph.w = ch->width; + ch->glyph.h = ch->height; + ch->glyph.data = NULL; + return 0; + } + if(fseek(font->in, ch->offset, SEEK_SET) == -1) + return -1; + ch->glyph.data = get_char(font->in, + ch->width, ch->height, ch->flags); + if(ch->glyph.data) { + /* restore original settings */ + ch->glyph.x = ch->x; + ch->glyph.y = ch->y; + ch->glyph.w = ch->width; + ch->glyph.h = ch->height; + } else + return -1; + ch->loaded = 1; + return 0; +} diff --git a/backend/dvi/mdvi-lib/private.h b/backend/dvi/mdvi-lib/private.h new file mode 100644 index 00000000..9f89dc70 --- /dev/null +++ b/backend/dvi/mdvi-lib/private.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef _MDVI_PRIVATE_H +#define _MDVI_PRIVATE_H 1 + +#define HAVE_PROTOTYPES 1 + +#if STDC_HEADERS +# /* kpathsea's headers (wrongly!) redefine strchr() and strrchr() to +# non ANSI C functions if HAVE_STRCHR and HAVE_STRRCHR are not defined. +# */ +# ifndef HAVE_STRCHR +# define HAVE_STRCHR +# endif +# ifndef HAVE_STRRCHR +# define HAVE_STRRCHR +# endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ISSP(p) (*(p) == ' ' || *(p) == '\t') +#define SKIPSP(p) while(ISSP(p)) p++ +#define SKIPNSP(p) while(*(p) && !ISSP(p)) p++ + +#ifdef ENABLE_NLS +#include +#define _(x) gettext(x) +#define _G(x) x +#else +#define _(x) x +#define _G(x) x +#endif /* ENABLE_NLS */ + +#if defined (__i386__) && defined (__GNUC__) && __GNUC__ >= 2 +#define _BREAKPOINT() do { __asm__ __volatile__ ("int $03"); } while(0) +#elif defined (__alpha__) && defined (__GNUC__) && __GNUC__ >= 2 +#define _BREAKPOINT() do { __asm__ __volatile__ ("bpt"); } while(0) +#else /* !__i386__ && !__alpha__ */ +#define _BREAKPOINT() +#endif /* __i386__ */ + +#endif /* _MDVI_PRIVATE_H */ diff --git a/backend/dvi/mdvi-lib/setup.c b/backend/dvi/mdvi-lib/setup.c new file mode 100644 index 00000000..ba0c545d --- /dev/null +++ b/backend/dvi/mdvi-lib/setup.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +#include "mdvi.h" +#include "private.h" + +void mdvi_init_kpathsea(const char *program, + const char *mfmode, const char *font, int dpi, + const char *texmfcnf) +{ + const char *p; + + /* Stop meaningless output generation. */ + kpse_make_tex_discard_errors = FALSE; + + p = strrchr(program, '/'); + p = (p ? p + 1 : program); + kpse_set_program_name(program, p); + kpse_init_prog(p, dpi, mfmode, font); + kpse_set_program_enabled(kpse_any_glyph_format, 1, kpse_src_compile); + kpse_set_program_enabled(kpse_pk_format, 1, kpse_src_compile); + kpse_set_program_enabled(kpse_tfm_format, 1, kpse_src_compile); + kpse_set_program_enabled(kpse_ofm_format, 1, kpse_src_compile); + if (texmfcnf != NULL) + xputenv("TEXMFCNF", texmfcnf); +} + diff --git a/backend/dvi/mdvi-lib/sp-epsf.c b/backend/dvi/mdvi-lib/sp-epsf.c new file mode 100644 index 00000000..703a9c8c --- /dev/null +++ b/backend/dvi/mdvi-lib/sp-epsf.c @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* postscript specials */ + +#include +#include +#include +#include +#include + +#include "mdvi.h" +#include "private.h" + +typedef struct { + double ox; + double oy; + double bw; + double bh; + double angle; +} EpsfBox; + +#define LLX 0 +#define LLY 1 +#define URX 2 +#define URY 3 +#define RWI 4 +#define RHI 5 +#define HOFF 6 +#define VOFF 7 +#define HSIZE 8 +#define VSIZE 9 +#define HSCALE 10 +#define VSCALE 11 +#define ANGLE 12 +#define CLIP 13 + +void epsf_special __PROTO((DviContext *dvi, char *prefix, char *arg)); + +/* Note: the given strings are modified in place */ +static char *parse_epsf_special(EpsfBox *box, char **ret, + char *prefix, char *arg) +{ + static struct { + char *name; + int has_arg; + char *value; + } keys[] = { + {"llx", 1, "0"}, + {"lly", 1, "0"}, + {"urx", 1, "0"}, + {"ury", 1, "0"}, + {"rwi", 1, "0"}, + {"rhi", 1, "0"}, + {"hoffset", 1, "0"}, + {"voffset", 1, "0"}, + {"hsize", 1, "612"}, + {"vsize", 1, "792"}, + {"hscale", 1, "100"}, + {"vscale", 1, "100"}, + {"angle", 1, "0"}, + {"clip", 0, "0"} + }; +#define NKEYS (sizeof(keys) / sizeof(keys[0])) + char *ptr; + char *filename; + int quoted; + double value[NKEYS]; + Uchar present[NKEYS]; + Buffer buffer; + char *name; + int i; + double originx; + double originy; + double hsize; + double vsize; + double hscale; + double vscale; + + /* this special has the form + * ["]file.ps["] [key=valye]* + */ + + /* scan the filename */ + while(*arg == ' ' || *arg == '\t') + arg++; + + /* make a copy of the string */ + ptr = arg; + + if(*ptr == '"') + for(name = ++ptr; *ptr && *ptr != '"'; ptr++); + else + for(name = ptr; *ptr && *ptr != ' ' && *ptr != '\t'; ptr++); + if(ptr == name) + return NULL; + *ptr++ = 0; + filename = name; + + /* reset values to defaults */ + for(i = 0; i < NKEYS; i++) { + value[i] = atof(keys[i].value); + present[i] = 0; + } + + buff_init(&buffer); + buff_add(&buffer, "@beginspecial ", 0); + + quoted = 0; + while(*ptr) { + const char *keyname; + char *val; + char *p; + + while(*ptr == ' ' || *ptr == '\t') + ptr++; + keyname = ptr; + + /* get the whole key=value pair */ + for(; *ptr && *ptr != ' ' && *ptr != '\t'; ptr++); + + if(*ptr) *ptr++ = 0; + /* now we shouldn't touch `ptr' anymore */ + + /* now work on this pair */ + p = strchr(keyname, '='); + if(p == NULL) + val = NULL; + else { + *p++ = 0; + if(*p == '"') { + val = ++p; + /* skip until closing quote */ + while(*p && *p != '"') + p++; + if(*p != '"') + mdvi_warning( + _("%s: malformed value for key `%s'\n"), + filename, keyname); + } else + val = p; + } + + /* lookup the key */ + for(i = 0; i < NKEYS; i++) + if(STRCEQ(keys[i].name, keyname)) + break; + if(i == NKEYS) { + mdvi_warning(_("%s: unknown key `%s' ignored\n"), + filename, keyname); + continue; + } + if(keys[i].has_arg && val == NULL) { + mdvi_warning(_("%s: no argument for key `%s', using defaults\n"), + filename, keyname); + val = keys[i].value; + } else if(!keys[i].has_arg && val) { + mdvi_warning(_("%s: argument `%s' ignored for key `%s'\n"), + filename, val, keyname); + val = NULL; + } + if(val) + value[i] = atof(val); + + /* put the argument */ + buff_add(&buffer, val, 0); + buff_add(&buffer, " @", 2); + buff_add(&buffer, keyname, 0); + buff_add(&buffer, " ", 1); + + /* remember that this option was given */ + present[i] = 0xff; + } + buff_add(&buffer, " @setspecial", 0); + + /* now compute the bounding box (code comes from dvips) */ + originx = 0; + originy = 0; + hscale = 1; + vscale = 1; + hsize = 0; + vsize = 0; + + if(present[HSIZE]) + hsize = value[HSIZE]; + if(present[VSIZE]) + vsize = value[VSIZE]; + if(present[HOFF]) + originx = value[HOFF]; + if(present[VOFF]) + originy = value[VOFF]; + if(present[HSCALE]) + hscale = value[HSCALE] / 100.0; + if(present[VSCALE]) + vscale = value[VSCALE] / 100.0; + if(present[URX] && present[LLX]) + hsize = value[URX] - value[LLX]; + if(present[URY] && present[LLY]) + vsize = value[URY] - value[LLY]; + if(present[RWI] || present[RHI]) { + if(present[RWI] && !present[RHI]) + hscale = vscale = value[RWI] / (10.0 * hsize); + else if(present[RHI] && !present[RWI]) + hscale = vscale = value[RHI] / (10.0 * vsize); + else { + hscale = value[RWI] / (10.0 * hsize); + vscale = value[RHI] / (10.0 * vsize); + } + } + + box->ox = originx; + box->oy = originy; + box->bw = hsize * hscale; + box->bh = vsize * vscale; + box->angle = value[ANGLE]; + + *ret = buffer.data; + + return filename; +} + +void epsf_special(DviContext *dvi, char *prefix, char *arg) +{ + char *file; + char *special; + char *psfile; + char *tmp; + EpsfBox box = {0, 0, 0, 0}; + int x, y; + int w, h; + double xf, vf; + struct stat buf; + + file = parse_epsf_special(&box, &special, prefix, arg); + if (file != NULL) + mdvi_free (special); + + xf = dvi->params.dpi * dvi->params.mag / (72.0 * dvi->params.hshrink); + vf = dvi->params.vdpi * dvi->params.mag / (72.0 * dvi->params.vshrink); + w = FROUND(box.bw * xf); + h = FROUND(box.bh * vf); + x = FROUND(box.ox * xf) + dvi->pos.hh; + y = FROUND(box.oy * vf) + dvi->pos.vv - h + 1; + + if (!file || !dvi->device.draw_ps) { + dvi->device.draw_rule (dvi, x, y, w, h, 0); + return; + } + + if (file[0] == '/') { /* Absolute path */ + if (stat (file, &buf) == 0) + dvi->device.draw_ps (dvi, file, x, y, w, h); + else + dvi->device.draw_rule (dvi, x, y, w, h, 0); + return; + } + + tmp = mdvi_strrstr (dvi->filename, "/"); + if (tmp) { /* Document directory */ + int path_len = strlen (dvi->filename) - strlen (tmp + 1); + int file_len = strlen (file); + + psfile = mdvi_malloc (path_len + file_len + 1); + psfile[0] = '\0'; + strncat (psfile, dvi->filename, path_len); + strncat (psfile, file, file_len); + + if (stat (psfile, &buf) == 0) { + dvi->device.draw_ps (dvi, psfile, x, y, w, h); + mdvi_free (psfile); + + return; + } + + mdvi_free (psfile); + } + + psfile = mdvi_build_path_from_cwd (file); + if (stat (psfile, &buf) == 0) { /* Current working dir */ + dvi->device.draw_ps (dvi, psfile, x, y, w, h); + mdvi_free (psfile); + + return; + } + + mdvi_free (psfile); + + psfile = kpse_find_pict (file); + if (psfile) { /* kpse */ + dvi->device.draw_ps (dvi, psfile, x, y, w, h); + } else { + dvi->device.draw_rule(dvi, x, y, w, h, 0); + } + + free (psfile); +} diff --git a/backend/dvi/mdvi-lib/special.c b/backend/dvi/mdvi-lib/special.c new file mode 100644 index 00000000..e4832544 --- /dev/null +++ b/backend/dvi/mdvi-lib/special.c @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include "mdvi.h" +#include "private.h" + +#if defined(WITH_REGEX_SPECIALS) && defined(HAVE_REGEX_H) +#include +#endif + +typedef struct _DviSpecial { + struct _DviSpecial *next; + struct _DviSpecial *prev; + char *label; + char *prefix; + size_t plen; +#ifdef WITH_REGEX_SPECIALS + regex_t reg; + int has_reg; +#endif + DviSpecialHandler handler; +} DviSpecial; + +static ListHead specials = {NULL, NULL, 0}; + +#define SPECIAL(x) \ + void x __PROTO((DviContext *, const char *, const char *)) + +static SPECIAL(sp_layer); +extern SPECIAL(epsf_special); +extern SPECIAL(do_color_special); + +static struct { + char *label; + char *prefix; + char *regex; + DviSpecialHandler handler; +} builtins[] = { + {"Layers", "layer", NULL, sp_layer}, + {"EPSF", "psfile", NULL, epsf_special} +}; +#define NSPECIALS (sizeof(builtins) / sizeof(builtins[0])) +static int registered_builtins = 0; + +static void register_builtin_specials(void) +{ + int i; + + ASSERT(registered_builtins == 0); + registered_builtins = 1; + for(i = 0; i < NSPECIALS; i++) + mdvi_register_special( + builtins[i].label, + builtins[i].prefix, + builtins[i].regex, + builtins[i].handler, + 1 /* replace if exists */); +} + +static DviSpecial *find_special_prefix(const char *prefix) +{ + DviSpecial *sp; + + /* should have a hash table here, but I'm so lazy */ + for(sp = (DviSpecial *)specials.head; sp; sp = sp->next) { + if(STRCEQ(sp->prefix, prefix)) + break; + } + return sp; +} + +int mdvi_register_special(const char *label, const char *prefix, + const char *regex, DviSpecialHandler handler, int replace) +{ + DviSpecial *sp; + int newsp = 0; + + if(!registered_builtins) + register_builtin_specials(); + + sp = find_special_prefix(prefix); + if(sp == NULL) { + sp = xalloc(DviSpecial); + sp->prefix = mdvi_strdup(prefix); + newsp = 1; + } else if(!replace) + return -1; + else { + mdvi_free(sp->label); + sp->label = NULL; + } + +#ifdef WITH_REGEX_SPECIALS + if(!newsp && sp->has_reg) { + regfree(&sp->reg); + sp->has_reg = 0; + } + if(regex && regcomp(&sp->reg, regex, REG_NOSUB) != 0) { + if(newsp) { + mdvi_free(sp->prefix); + mdvi_free(sp); + } + return -1; + } + sp->has_reg = (regex != NULL); +#endif + sp->handler = handler; + sp->label = mdvi_strdup(label); + sp->plen = strlen(prefix); + if(newsp) + listh_prepend(&specials, LIST(sp)); + DEBUG((DBG_SPECIAL, + "New \\special handler `%s' with prefix `%s'\n", + label, prefix)); + return 0; +} + +int mdvi_unregister_special(const char *prefix) +{ + DviSpecial *sp; + + sp = find_special_prefix(prefix); + if(sp == NULL) + return -1; + mdvi_free(sp->prefix); +#ifdef WITH_REGEX_SPECIALS + if(sp->has_reg) + regfree(&sp->reg); +#endif + listh_remove(&specials, LIST(sp)); + mdvi_free(sp); + return 0; +} + +#define IS_PREFIX_DELIMITER(x) (strchr(" \t\n:=", (x)) != NULL) + +int mdvi_do_special(DviContext *dvi, char *string) +{ + char *prefix; + char *ptr; + DviSpecial *sp; + + if(!registered_builtins) { + } + + if(!string || !*string) + return 0; + + /* skip leading spaces */ + while(*string && isspace(*string)) + string++; + + DEBUG((DBG_SPECIAL, "Looking for a handler for `%s'\n", string)); + + /* now try to find a match */ + ptr = string; + for(sp = (DviSpecial *)specials.head; sp; sp = sp->next) { +#ifdef WITH_REGEX_SPECIALS + if(sp->has_reg && !regexec(&sp->reg, ptr, 0, 0, 0)) + break; +#endif + /* check the prefix */ + if(STRNCEQ(sp->prefix, ptr, sp->plen)) { + ptr += sp->plen; + break; + } + } + + if(sp == NULL) { + DEBUG((DBG_SPECIAL, "None found\n")); + return -1; + } + + /* extract the prefix */ + if(ptr == string) { + prefix = NULL; + DEBUG((DBG_SPECIAL, + "REGEX match with `%s' (arg `%s')\n", + sp->label, ptr)); + } else { + if(*ptr) *ptr++ = 0; + prefix = string; + DEBUG((DBG_SPECIAL, + "PREFIX match with `%s' (prefix `%s', arg `%s')\n", + sp->label, prefix, ptr)); + } + + /* invoke the handler */ + sp->handler(dvi, prefix, ptr); + + return 0; +} + +void mdvi_flush_specials(void) +{ + DviSpecial *sp, *list; + + + for(list = (DviSpecial *)specials.head; (sp = list); ) { + list = sp->next; + if(sp->prefix) mdvi_free(sp->prefix); + if(sp->label) mdvi_free(sp->label); +#ifdef WITH_REGEX_SPECIALS + if(sp->has_reg) + regfree(&sp->reg); +#endif + mdvi_free(sp); + } + specials.head = NULL; + specials.tail = NULL; + specials.count = 0; +} + +/* some builtin specials */ + +void sp_layer(DviContext *dvi, const char *prefix, const char *arg) +{ + if(STREQ("push", arg)) + dvi->curr_layer++; + else if(STREQ("pop", arg)) { + if(dvi->curr_layer) + dvi->curr_layer--; + else + mdvi_warning(_("%s: tried to pop top level layer\n"), + dvi->filename); + } else if(STREQ("reset", arg)) + dvi->curr_layer = 0; + DEBUG((DBG_SPECIAL, "Layer level: %d\n", dvi->curr_layer)); +} + diff --git a/backend/dvi/mdvi-lib/sysdeps.h b/backend/dvi/mdvi-lib/sysdeps.h new file mode 100644 index 00000000..c77d7651 --- /dev/null +++ b/backend/dvi/mdvi-lib/sysdeps.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef _SYSDEP_H +#define _SYSDEP_H 1 + +/* + * The purpose of this file is to define symbols that describe the + * system-dependent features we use. Namely, byte order, native integer + * types of various sizes, and safe pointer<->integer conversion. + */ + +#include "config.h" + +#ifdef WORDS_BIGENDIAN +#define WORD_BIG_ENDIAN 1 +#else +#define WORD_LITTLE_ENDIAN 1 +#endif + +typedef unsigned long Ulong; +typedef unsigned int Uint; +typedef unsigned short Ushort; +typedef unsigned char Uchar; + +/* this one's easy */ +typedef unsigned char Uint8; +typedef char Int8; + +/* define a datatype for 32bit integers (either int or long) */ +#if SIZEOF_LONG == 4 +typedef unsigned long Uint32; +typedef long Int32; +#else /* SIZEOF_LONG != 4 */ +#if SIZEOF_INT == 4 +typedef unsigned int Uint32; +typedef int Int32; +#else /* SIZEOF_INT != 4 */ +#ifdef __cplusplus +#include "No.appropriate.32bit.native.type.found.Fix.sysdeps.h" +#else +#error No appropriate 32bit native type found. Fix sysdeps.h +#endif /* ! __cplusplus */ +#endif /* SIZEOF_INT != 4 */ +#endif /* SIZEOF_LONG != 4 */ + +/* now 16bit integers (one of long, int or short) */ +#if SIZEOF_SHORT == 2 +typedef unsigned short Uint16; +typedef short Int16; +#else /* SIZEOF_SHORT != 2 */ +#if SIZEOF_INT == 2 +typedef unsigned int Uint16; +typedef short Int16; +#else /* SIZEOF_INT != 2 */ +#ifdef __cplusplus +#include "No.appropriate.16bit.native.type.found.Fix.sysdeps.h" +#else +#error No appropriate 16bit native type found. Fix sysdeps.h +#endif /* ! __cplusplus */ +#endif /* SIZEOF_INT != 2 */ +#endif /* SIZEOF_SHORT != 2 */ + +/* + * An integer type to convert to and from pointers safely. All we do here is + * look for an integer type with the same size as a pointer. + */ +#if SIZEOF_LONG == SIZEOF_VOID_P +typedef unsigned long UINT; +typedef long INT; +#else +#if SIZEOF_INT == SIZEOF_VOID_P +typedef unsigned int UINT; +typedef int INT; +#else +#if SIZEOF_SHORT == SIZEOF_VOID_P +typedef unsigned short UINT; +typedef short INT; +#else +#ifdef __cplusplus +#include "No.native.pointer-compatible.integer.type.found.Fix.sysdeps.h" +#else +#error No native pointer-compatible integer type found. Fix sysdeps.h +#endif +#endif +#endif +#endif + +/* nice, uh? */ +typedef void *Pointer; + +/* macros to do the safe pointer <-> integer conversions */ +#define Ptr2Int(x) ((INT)((Pointer)(x))) +#define Int2Ptr(x) ((Pointer)((INT)(x))) + +#ifdef _NO_PROTO +#define __PROTO(x) () +#else +#define __PROTO(x) x +#endif + +#endif /* _SYSDEP_H */ diff --git a/backend/dvi/mdvi-lib/t1.c b/backend/dvi/mdvi-lib/t1.c new file mode 100644 index 00000000..bc910ba6 --- /dev/null +++ b/backend/dvi/mdvi-lib/t1.c @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * Type1 font support for MDVI + * + * We use T1lib only as a rasterizer, not to draw glyphs. + */ + +#include +#include "mdvi.h" + +#ifdef WITH_TYPE1_FONTS + +#include +#include +#include "private.h" + +static int t1lib_initialized = 0; + +typedef struct t1info { + struct t1info *next; + struct t1info *prev; + char *fontname; /* (short) name of this font */ + int t1id; /* T1lib's id for this font */ + int hasmetrics; /* have we processed this font? */ + TFMInfo *tfminfo; /* TFM data is shared */ + DviFontMapInfo mapinfo; + DviEncoding *encoding; +} T1Info; + +static void t1_font_remove __PROTO((T1Info *)); +static int t1_load_font __PROTO((DviParams *, DviFont *)); +static int t1_font_get_glyph __PROTO((DviParams *, DviFont *, int)); +static void t1_font_shrink_glyph + __PROTO((DviContext *, DviFont *, DviFontChar *, DviGlyph *)); +static void t1_free_data __PROTO((DviFont *)); +static void t1_reset_font __PROTO((DviFont *)); +static char *t1_lookup_font __PROTO((const char *, Ushort *, Ushort *)); + +/* only symbol exported by this file */ +DviFontInfo t1_font_info = { + "Type1", + 1, /* scaling supported by format */ + t1_load_font, + t1_font_get_glyph, + t1_font_shrink_glyph, + mdvi_shrink_glyph_grey, + t1_free_data, + t1_reset_font, + t1_lookup_font, /* lookup */ + kpse_type1_format, + NULL +}; + +/* this seems good enough for most DVI files */ +#define T1_HASH_SIZE 31 + +/* If these parameters change, we must delete all size information + * in all fonts, and reset the device resolutions in T1lib */ +static int t1lib_xdpi = -1; +static int t1lib_ydpi = -1; + +static ListHead t1fonts = {NULL, NULL, 0}; +static DviHashTable t1hash; + +/* Type1 fonts need their own `lookup' function. Here is how it works: + * First we try to find the font by its given name. If that fails, we + * query the font maps. A typical font map entry may contain the line + * + * ptmr8rn Times-Roman ".82 ExtendFont TeXBase1Encoding ReEncodeFont" <8r.enc private; + + if(info == NULL) + return; + DEBUG((DBG_FONTS, "(t1) resetting font `%s'\n", font->fontname)); + /* just mark the font as not having metric info. It will be reset + * automatically later */ + info->hasmetrics = 0; +} + +static void t1_transform_font(T1Info *info) +{ + if(!info->hasmetrics && info->encoding != NULL) { + DEBUG((DBG_TYPE1, "(t1) %s: encoding with vector `%s'\n", + info->fontname, info->encoding->name)); + T1_DeleteAllSizes(info->t1id); + if(T1_ReencodeFont(info->t1id, info->encoding->vector) < 0) + mdvi_warning(_("%s: could not encode font\n"), info->fontname); + } + if(info->mapinfo.slant) { + DEBUG((DBG_TYPE1, "(t1) %s: slanting by %.3f\n", + info->fontname, + MDVI_FMAP_SLANT(&info->mapinfo))); + T1_SlantFont(info->t1id, + MDVI_FMAP_SLANT(&info->mapinfo)); + } + if(info->mapinfo.extend) { + DEBUG((DBG_TYPE1, "(t1) %s: extending by %.3f\n", + info->fontname, + MDVI_FMAP_EXTEND(&info->mapinfo))); + T1_ExtendFont(info->t1id, + MDVI_FMAP_EXTEND(&info->mapinfo)); + } +} + +/* if this function is called, we really need this font */ +static int t1_really_load_font(DviParams *params, DviFont *font, T1Info *info) +{ + int i; + T1Info *old; + int t1id; + int copied; + int status; + + DEBUG((DBG_TYPE1, "(t1) really_load_font(%s)\n", info->fontname)); + + /* if the parameters changed, reset T1lib */ + if(t1lib_xdpi != params->dpi || t1lib_ydpi != params->vdpi) + t1_reset_resolution(params->dpi, params->vdpi); + + /* if we already have a T1lib id, do nothing */ + if(info->t1id != -1) { + info->hasmetrics = 1; + /* apply slant and extend again */ + t1_transform_font(info); + return 0; + } + + /* before we even attempt to load the font, make sure we have metric + * data for it */ + info->tfminfo = mdvi_ps_get_metrics(info->fontname); + if(info->tfminfo == NULL) { + DEBUG((DBG_FONTS, + "(t1) %s: no metric data, font ignored\n", + info->fontname)); + goto t1_error; + } + /* fix this */ + font->design = info->tfminfo->design; + + /* check if we have a font with this name (maybe at a different size) */ + old = (T1Info *)mdvi_hash_lookup(&t1hash, (unsigned char *)info->fontname); + if(old == info) { + /* let's avoid confusion */ + old = NULL; + } + if(old && old->t1id != -1) { + /* let's take advantage of T1lib's font sharing */ + t1id = T1_CopyFont(old->t1id); + DEBUG((DBG_TYPE1, "(t1) %s -> %d (CopyFont)\n", + info->fontname, t1id)); + copied = 1; + } else { + t1id = T1_AddFont(font->filename); + DEBUG((DBG_TYPE1, "(t1) %s -> %d (AddFont)\n", + info->fontname, t1id)); + copied = 0; + } + if(t1id < 0) + goto t1_error; + info->t1id = t1id; + + /* + * a minor optimization: If the old font in the hash table has + * not been loaded yet, replace it by this one, so we can use + * CopyFont later. + */ + if(old && old->t1id == -1) { + DEBUG((DBG_TYPE1, "(t1) font `%s' exchanged in hash table\n", + info->fontname)); + mdvi_hash_remove(&t1hash, (unsigned char *)old->fontname); + mdvi_hash_add(&t1hash, (unsigned char *)info->fontname, + info, MDVI_HASH_UNCHECKED); + } + + /* now let T1lib load it */ + if(!copied && T1_LoadFont(info->t1id) < 0) { + DEBUG((DBG_TYPE1, "(t1) T1_LoadFont(%d) failed with error %d\n", + info->t1id, T1_errno)); + goto t1_error; + } + DEBUG((DBG_TYPE1, "(t1) T1_LoadFont(%d) -> Ok\n", info->t1id)); + + /* get information from the fontmap */ + status = mdvi_query_fontmap(&info->mapinfo, info->fontname); + if(!status && info->mapinfo.encoding) + info->encoding = mdvi_request_encoding(info->mapinfo.encoding); + t1_transform_font(info); + + i = info->tfminfo->hic - info->tfminfo->loc + 1; + if(i != font->hic - font->loc + 1) { + /* reset to optimal size */ + font->chars = mdvi_realloc(font->chars, i * sizeof(DviFontChar)); + } + + /* get the scaled characters metrics */ + get_tfm_chars(params, font, info->tfminfo, 0); + info->hasmetrics = 1; + + DEBUG((DBG_TYPE1, "(t1) font `%s' really-loaded\n", info->fontname)); + return 0; + +t1_error: + /* some error does not allows us to use this font. We need to reset + * the font structure, so the font system can try to read this + * font in a different class */ + + /* first destroy the private data */ + t1_font_remove(info); + /* now reset all chars -- this is the important part */ + mdvi_free(font->chars); + font->chars = NULL; + font->loc = font->hic = 0; + return -1; +} + +static int init_t1lib(DviParams *params) +{ + int t1flags; + +#ifdef WORD_LITTLE_ENDIAN + /* try making T1lib use bitmaps in our format, but if this + * fails we'll convert the bitmap ourselves */ + T1_SetBitmapPad(BITMAP_BITS); +#endif + T1_SetDeviceResolutions((float)params->dpi, (float)params->vdpi); + t1flags = IGNORE_CONFIGFILE|IGNORE_FONTDATABASE|T1_NO_AFM; + if(DEBUGGING(TYPE1)) + t1flags |= LOGFILE; + if(T1_InitLib(t1flags) == NULL) + return (t1lib_initialized = -1); + if(DEBUGGING(TYPE1)) { + DEBUG((DBG_TYPE1, "T1lib debugging output saved in t1lib.log\n")); + T1_SetLogLevel(T1LOG_DEBUG); + } + /* initialize our hash table, but don't allocate memory for it + * until we use it */ + mdvi_hash_init(&t1hash); + DEBUG((DBG_TYPE1, "(t1) t1lib %s initialized -- resolution is (%d, %d), pad is %d bits\n", + T1_GetLibIdent(), params->dpi, params->vdpi, T1_GetBitmapPad())); + t1lib_initialized = 1; + t1lib_xdpi = params->dpi; + t1lib_ydpi = params->vdpi; + return 0; +} + +static int t1_load_font(DviParams *params, DviFont *font) +{ + T1Info *info; + int i; + + if(t1lib_initialized < 0) + return -1; + else if(t1lib_initialized == 0 && init_t1lib(params) < 0) + return -1; + + if(font->in != NULL) { + /* we don't need this */ + fclose(font->in); + font->in = NULL; + } + + info = xalloc(T1Info); + + /* + * mark the font as `unregistered' with T1lib. It will + * be added when we actually use it + */ + info->t1id = -1; + + /* add the font to our list */ + info->fontname = font->fontname; + info->hasmetrics = 0; + info->encoding = NULL; + info->mapinfo.psname = NULL; + info->mapinfo.encoding = NULL; + info->mapinfo.fontfile = NULL; + info->mapinfo.extend = 0; + info->mapinfo.slant = 0; + info->encoding = NULL; + + /* create the hash table if we have not done so yet */ + if(t1hash.nbucks == 0) + mdvi_hash_create(&t1hash, T1_HASH_SIZE); + mdvi_hash_add(&t1hash, (unsigned char *) info->fontname, info, MDVI_HASH_UNIQUE); + listh_append(&t1fonts, LIST(info)); + + font->private = info; + + /* reset everything */ + font->chars = xnalloc(DviFontChar, 256); + font->loc = 0; + font->hic = 255; + for(i = 0; i < 256; i++) { + font->chars[i].code = i; + font->chars[i].offset = 1; + font->chars[i].loaded = 0; + font->chars[i].glyph.data = NULL; + font->chars[i].shrunk.data = NULL; + font->chars[i].grey.data = NULL; + } + + return 0; +} + +#define GLYPH_WIDTH(g) \ + ((g)->metrics.rightSideBearing - (g)->metrics.leftSideBearing) +#define GLYPH_HEIGHT(g) \ + ((g)->metrics.ascent - (g)->metrics.descent) + +static inline BITMAP *t1_glyph_bitmap(GLYPH *glyph) +{ + int w, h, pad; + + w = GLYPH_WIDTH(glyph); + h = GLYPH_HEIGHT(glyph); + + if(!w || !h) + return MDVI_GLYPH_EMPTY; + + pad = T1_GetBitmapPad(); + return bitmap_convert_lsb8((unsigned char *)glyph->bits, w, h, ROUND(w, pad) * (pad >> 3)); +} + +static void t1_font_shrink_glyph(DviContext *dvi, DviFont *font, DviFontChar *ch, DviGlyph *dest) +{ + double size; + GLYPH *glyph; + T1Info *info; + T1_TMATRIX matrix; + + info = (T1Info *)font->private; + ASSERT(info != NULL); + + DEBUG((DBG_TYPE1, "(t1) shrinking glyph for character %d in `%s' (%d,%d)\n", + ch->code, font->fontname, ch->width, ch->height)); + size = (double)font->scale / (dvi->params.tfm_conv * 0x100000); + size = 72.0 * size / 72.27; + matrix.cxx = 1.0/(double)dvi->params.hshrink; + matrix.cyy = 1.0/(double)dvi->params.vshrink; + matrix.cxy = 0.0; + matrix.cyx = 0.0; + glyph = T1_SetChar(info->t1id, ch->code, (float)size, &matrix); + + dest->data = t1_glyph_bitmap(glyph); + dest->x = -glyph->metrics.leftSideBearing; + dest->y = glyph->metrics.ascent; + dest->w = GLYPH_WIDTH(glyph); + dest->h = GLYPH_HEIGHT(glyph); + +#ifndef NODEBUG + if(DEBUGGING(BITMAP_DATA)) { + DEBUG((DBG_BITMAP_DATA, + "(t1) %s: t1_shrink_glyph(%d): (%dw,%dh,%dx,%dy) -> (%dw,%dh,%dx,%dy)\n", + ch->glyph.w, ch->glyph.h, ch->glyph.x, ch->glyph.y, + dest->w, dest->h, dest->x, dest->y)); + bitmap_print(stderr, (BITMAP *)dest->data); + } +#endif + /* transform the glyph - we could do this with t1lib, but we do + * it ourselves for now */ + font_transform_glyph(dvi->params.orientation, dest); +} + +static int t1_font_get_glyph(DviParams *params, DviFont *font, int code) +{ + T1Info *info = (T1Info *)font->private; + GLYPH *glyph; + DviFontChar *ch; + double size; + T1_TMATRIX matrix; + int dpi; + + ASSERT(info != NULL); + if(!info->hasmetrics && t1_really_load_font(params, font, info) < 0) + return -1; + ch = FONTCHAR(font, code); + if(!ch || !glyph_present(ch)) + return -1; + ch->loaded = 1; + if(!ch->width || !ch->height) { + ch->glyph.x = ch->x; + ch->glyph.y = ch->y; + ch->glyph.w = ch->width; + ch->glyph.h = ch->height; + ch->glyph.data = NULL; + return 0; + } + + /* load the glyph with T1lib (this is done only once for each glyph) */ + + /* get size in TeX points (tfm_conv includes dpi and magnification) */ + size = (double)font->scale / (params->tfm_conv * 0x100000); + /* and transform into PostScript points */ + size = 72.0 * size / 72.27; + + dpi = Max(font->hdpi, font->vdpi); + /* we don't want the glyph to be cached twice (once by us, another by + * T1lib), so we use an identity matrix to tell T1lib not to keep the + * glyph around */ + matrix.cxx = (double)font->hdpi / dpi; + matrix.cyy = (double)font->vdpi / dpi; + matrix.cxy = matrix.cyx = 0.0; + glyph = T1_SetChar(info->t1id, ch->code, (float)size, &matrix); + if(glyph == NULL) { + ch->glyph.x = ch->x; + ch->glyph.y = ch->y; + ch->glyph.w = ch->width; + ch->glyph.h = ch->height; + ch->glyph.data = NULL; + ch->missing = 1; + return 0; + } + /* and make it a bitmap */ + ch->glyph.data = t1_glyph_bitmap(glyph); + ch->glyph.x = -glyph->metrics.leftSideBearing; + ch->glyph.y = glyph->metrics.ascent; + ch->glyph.w = GLYPH_WIDTH(glyph); + ch->glyph.h = GLYPH_HEIGHT(glyph); + + /* let's also fix the glyph's origin + * (which is not contained in the TFM) */ + ch->x = ch->glyph.x; + ch->y = ch->glyph.y; + /* let's fix these too */ + ch->width = ch->glyph.w; + ch->height = ch->glyph.h; + + return 0; +} + +static void t1_font_remove(T1Info *info) +{ + T1Info *old; + + /* first remove it from our list */ + listh_remove(&t1fonts, LIST(info)); + + /* it it's in the hash table, we may need to replace this by another font */ + old = (T1Info *)mdvi_hash_lookup(&t1hash, (unsigned char *)info->fontname); + if(old == info) { + mdvi_hash_remove(&t1hash, (unsigned char *) info->fontname); + /* go through the list and see if there is another + * font with this name */ + for(old = (T1Info *)t1fonts.head; old; old = old->next) + if(STREQ(old->fontname, info->fontname)) + break; + if(old != NULL) + mdvi_hash_add(&t1hash, (unsigned char *) old->fontname, old, + MDVI_HASH_UNCHECKED); + } + /* release our encoding vector */ + if(info->encoding) { + DEBUG((DBG_TYPE1, "(t1) %s: releasing vector `%s'\n", + info->fontname, info->encoding->name)); + mdvi_release_encoding(info->encoding, 1); + } + + /* now get rid of it */ + if(info->t1id != -1) { + DEBUG((DBG_TYPE1, "(t1) %s: T1_DeleteFont(%d)\n", + info->fontname, info->t1id)); + T1_DeleteFont(info->t1id); + } else + DEBUG((DBG_TYPE1, "(t1) %s: not loaded yet, DeleteFont skipped\n", + info->fontname)); + + if(info->tfminfo) + free_font_metrics(info->tfminfo); + /*mdvi_free(info->fontname);*/ + mdvi_free(info); +} + +static void t1_free_data(DviFont *font) +{ + /* called after all the glyphs are destroyed */ + + if(font->private == NULL) { + /* this is perfectly normal, it just means the font has + * not been requested by MDVI yet */ + return; + } + + /* destroy this data */ + + t1_font_remove((T1Info *)font->private); + font->private = NULL; + + /* + * if this is the last T1 font, reset the T1 library + * It is important that we do this, because this is will be called + * when the resolution or the magnification changes. + */ + if(t1fonts.count == 0) { + DEBUG((DBG_TYPE1, "(t1) last font removed -- closing T1lib\n")); + T1_CloseLib(); + t1lib_initialized = 0; + t1lib_xdpi = -1; + t1lib_ydpi = -1; + } +} + +#endif /* WITH_TYPE1_FONTS */ diff --git a/backend/dvi/mdvi-lib/tfm.c b/backend/dvi/mdvi-lib/tfm.c new file mode 100644 index 00000000..f37de0be --- /dev/null +++ b/backend/dvi/mdvi-lib/tfm.c @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "mdvi.h" +#include "private.h" + +static int tfm_load_font __PROTO((DviParams *, DviFont *)); +static int tfm_font_get_glyph __PROTO((DviParams *, DviFont *, int)); + +DviFontInfo tfm_font_info = { + "TFM", + 0, /* scaling not supported by format */ + tfm_load_font, + tfm_font_get_glyph, + mdvi_shrink_box, + mdvi_shrink_box, + NULL, /* free */ + NULL, /* reset */ + NULL, /* lookup */ + kpse_tfm_format, + NULL +}; + +DviFontInfo ofm_font_info = { + "OFM", + 0, /* scaling not supported by format */ + tfm_load_font, + tfm_font_get_glyph, + mdvi_shrink_box, + mdvi_shrink_box, + NULL, /* free */ + NULL, /* reset */ + NULL, /* lookup */ + kpse_ofm_format, + NULL +}; + +DviFontInfo afm_font_info = { + "AFM", + 0, /* scaling not supported by format */ + tfm_load_font, + tfm_font_get_glyph, + mdvi_shrink_box, + mdvi_shrink_box, + NULL, /* free */ + NULL, /* reset */ + NULL, /* lookup */ + kpse_afm_format, + NULL +}; + +#define TYPENAME(font) \ + ((font)->search.info ? (font)->search.info : "none") + +/* + * Although it does not seem that way, this conversion is independent of the + * shrinking factors, within roundoff (that's because `conv' and `vconv' + * have already been scaled by hshrink and vshrink, repsectively). We + * should really use `dviconv' and `dvivconv', but I'm not so sure those + * should be moved to the DviParams structure. + */ +#define XCONV(x) FROUND(params->conv * (x) * params->hshrink) +#define YCONV(y) FROUND(params->vconv * (y) * params->vshrink) + +/* this is used quite often in several places, so I made it standalone */ +int get_tfm_chars(DviParams *params, DviFont *font, TFMInfo *info, int loaded) +{ + Int32 z, alpha, beta; + int n; + DviFontChar *ch; + TFMChar *ptr; + + n = info->hic - info->loc + 1; + if(n != FONT_GLYPH_COUNT(font)) { + font->chars = mdvi_realloc(font->chars, + n * sizeof(DviFontChar)); + } + font->loc = info->loc; + font->hic = info->hic; + ch = font->chars; + ptr = info->chars; + + /* Prepare z, alpha and beta for TFM width computation */ + TFMPREPARE(font->scale, z, alpha, beta); + + /* get the character metrics */ + for(n = info->loc; n <= info->hic; ch++, ptr++, n++) { + int a, b, c, d; + + ch->offset = ptr->present; + if(ch->offset == 0) + continue; + /* this is what we came here for */ + ch->tfmwidth = TFMSCALE(z, ptr->advance, alpha, beta); + /* scale all other TFM units (so they are in DVI units) */ + a = TFMSCALE(z, ptr->left, alpha, beta); + b = TFMSCALE(z, ptr->right, alpha, beta); + c = TFMSCALE(z, ptr->height, alpha, beta); + d = TFMSCALE(z, ptr->depth, alpha, beta); + + /* now convert to unscaled pixels */ + ch->width = XCONV(b - a); + ch->height = YCONV(c - d); + if(ch->height < 0) ch->height = -ch->height; + ch->x = XCONV(a); + ch->y = YCONV(c); + /* + * the offset is not used, but we might as well set it to + * something meaningful (and it MUST be non-zero) + */ + ch->flags = 0; + ch->code = n; + ch->glyph.data = NULL; + ch->grey.data = NULL; + ch->shrunk.data = NULL; + ch->loaded = loaded; + } + + return 0; +} + +/* + * We use this function as a last resort to find the character widths in a + * font The DVI rendering code can correctly skip over a glyph if it knows + * its TFM width, which is what we try to find here. + */ +static int tfm_load_font(DviParams *params, DviFont *font) +{ + TFMInfo *tfm; + int type; + + switch(font->search.info->kpse_type) { + case kpse_tfm_format: + type = DviFontTFM; + break; + case kpse_afm_format: + type = DviFontAFM; + break; + case kpse_ofm_format: + type = DviFontOFM; + break; + default: + return -1; + } + + /* we don't need this */ + if(font->in) { + fclose(font->in); + font->in = NULL; + } + tfm = get_font_metrics(font->fontname, type, font->filename); + if(tfm == NULL) + return -1; + + if(tfm->checksum && font->checksum && tfm->checksum != font->checksum) { + mdvi_warning(_("%s: Checksum mismatch (got %u, expected %u)\n"), + font->fontname, (unsigned)tfm->checksum, + (unsigned)font->checksum); + } + font->checksum = tfm->checksum; + font->design = tfm->design; + font->loc = 0; + font->hic = 0; + font->chars = NULL; + get_tfm_chars(params, font, tfm, 1); + + /* free everything */ + free_font_metrics(tfm); + + return 0; +} + +static int tfm_font_get_glyph(DviParams *params, DviFont *font, int code) +{ + DviFontChar *ch; + + ch = FONTCHAR(font, code); + if(!glyph_present(ch)) + return -1; + ch->glyph.x = ch->x; + ch->glyph.y = ch->y; + ch->glyph.w = ch->width; + ch->glyph.h = ch->height; + /* + * This has two purposes: (1) avoid unnecessary calls to this function, + * and (2) detect when the glyph data for a TFM font is actually used + * (we'll get a SEGV). Any occurrence of that is a bug. + */ + ch->glyph.data = MDVI_GLYPH_EMPTY; + + return 0; +} diff --git a/backend/dvi/mdvi-lib/tfmfile.c b/backend/dvi/mdvi-lib/tfmfile.c new file mode 100644 index 00000000..73ebf26a --- /dev/null +++ b/backend/dvi/mdvi-lib/tfmfile.c @@ -0,0 +1,747 @@ +/* tfmfile.c -- readers for TFM, AFM, OTFM-0 and OTFM-1 files */ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include /* tex-file.h needs this */ +#include +#include +#include +#include +#include + +#include "mdvi.h" +#include "private.h" + +#ifdef WITH_AFM_FILES +#undef TRUE +#undef FALSE +#include "afmparse.h" +#endif + +typedef struct tfmpool { + struct tfmpool *next; + struct tfmpool *prev; + char *short_name; + int links; + TFMInfo tfminfo; +} TFMPool; + +static ListHead tfmpool = {NULL, NULL, 0}; +static DviHashTable tfmhash; + +#define TFM_HASH_SIZE 31 + +#ifdef WORD_LITTLE_ENDIAN +static inline void swap_array(Uint32 *ptr, int n) +{ + Uint32 i; + + while(n-- > 0) { + i = *ptr; + *ptr++ = ((i & 0xff000000) >> 24) + | ((i & 0x00ff0000) >> 8) + | ((i & 0x0000ff00) << 8) + | ((i & 0x000000ff) << 24); + } +} +#endif + +#ifdef WITH_AFM_FILES + +static int __PROTO(ofm_load_file(const char *filename, TFMInfo *info)); + +/* reading of AFM files */ +/* macro to convert between AFM and TFM units */ +#define AFM2TFM(x) FROUND((double)(x) * 0x100000 / 1000) +int afm_load_file(const char *filename, TFMInfo *info) +{ + /* the information we want is: + * - tfmwidth + * - width and heights + * - character origins + */ + FontInfo *fi = NULL; + int status; + CharMetricInfo *cm; + FILE *in; + + in = fopen(filename, "rb"); + if(in == NULL) + return -1; + status = afm_parse_file(in, &fi, P_GM); + fclose(in); + + if(status != ok) { + mdvi_error(_("%s: Error reading AFM data\n"), filename); + return -1; + } + + /* aim high */ + info->chars = xnalloc(TFMChar, 256); + info->loc = 256; + info->hic = 0; + info->design = 0xa00000; /* fake -- 10pt */ + info->checksum = 0; /* no checksum */ + info->type = DviFontAFM; + mdvi_strncpy(info->coding, fi->gfi->encodingScheme, 63); + mdvi_strncpy(info->family, fi->gfi->familyName, 63); + + /* now get the data */ + for(cm = fi->cmi; cm < fi->cmi + fi->numOfChars; cm++) { + int code; + TFMChar *ch; + + code = cm->code; + if(code < 0 || code > 255) + continue; /* ignore it */ + ch = &info->chars[code]; + ch->present = 1; + if(code < info->loc) + info->loc = code; + if(code > info->hic) + info->hic = code; + ch->advance = AFM2TFM(cm->wx); + /* this is the `leftSideBearing' */ + ch->left = AFM2TFM(cm->charBBox.llx); + /* this is the height (ascent - descent) -- the sign is to follow + * TeX conventions, as opposed to Adobe's ones */ + ch->depth = -AFM2TFM(cm->charBBox.lly); + /* this is the width (rightSideBearing - leftSideBearing) */ + ch->right = AFM2TFM(cm->charBBox.urx); + /* this is the `ascent' */ + ch->height = AFM2TFM(cm->charBBox.ury); + } + + /* we don't need this anymore */ + afm_free_fontinfo(fi); + + /* optimize storage */ + if(info->loc > 0 || info->hic < 256) { + memmove(&info->chars[0], + &info->chars[info->loc], + (info->hic - info->loc + 1) * sizeof(TFMChar)); + info->chars = mdvi_realloc(info->chars, + (info->hic - info->loc + 1) * sizeof(TFMChar)); + } + + /* we're done */ + return 0; +} + +#endif /* WITH_AFM_FILES */ + +int tfm_load_file(const char *filename, TFMInfo *info) +{ + int lf, lh, bc, ec, nw, nh, nd, ne; + int i, n; + Uchar *tfm; + Uchar *ptr; + struct stat st; + int size; + FILE *in; + Int32 *cb; + Int32 *charinfo; + Int32 *widths; + Int32 *heights; + Int32 *depths; + Uint32 checksum; + + in = fopen(filename, "rb"); + if(in == NULL) + return -1; + tfm = NULL; + + DEBUG((DBG_FONTS, "(mt) reading TFM file `%s'\n", + filename)); + /* We read the entire TFM file into core */ + if(fstat(fileno(in), &st) < 0) + return -1; + if(st.st_size == 0) + goto bad_tfm; + + /* allocate a word-aligned buffer to hold the file */ + size = 4 * ROUND(st.st_size, 4); + if(size != st.st_size) + mdvi_warning(_("Warning: TFM file `%s' has suspicious size\n"), + filename); + tfm = (Uchar *)mdvi_malloc(size); + if(fread(tfm, st.st_size, 1, in) != 1) + goto error; + /* we don't need this anymore */ + fclose(in); + in = NULL; + + /* not a checksum, but serves a similar purpose */ + checksum = 0; + + ptr = tfm; + /* get the counters */ + lf = muget2(ptr); + lh = muget2(ptr); checksum += 6 + lh; + bc = muget2(ptr); + ec = muget2(ptr); checksum += ec - bc + 1; + nw = muget2(ptr); checksum += nw; + nh = muget2(ptr); checksum += nh; + nd = muget2(ptr); checksum += nd; + checksum += muget2(ptr); /* skip italics correction count */ + checksum += muget2(ptr); /* skip lig/kern table size */ + checksum += muget2(ptr); /* skip kern table size */ + ne = muget2(ptr); checksum += ne; + checksum += muget2(ptr); /* skip # of font parameters */ + + size = ec - bc + 1; + cb = (Int32 *)tfm; cb += 6 + lh; + charinfo = cb; cb += size; + widths = cb; cb += nw; + heights = cb; cb += nh; + depths = cb; + + if(widths[0] || heights[0] || depths[0] || + checksum != lf || bc - 1 > ec || ec > 255 || ne > 256) + goto bad_tfm; + + /* from this point on, no error checking is done */ + + /* now we're at the header */ + /* get the checksum */ + info->checksum = muget4(ptr); + /* get the design size */ + info->design = muget4(ptr); + /* get the coding scheme */ + if(lh > 2) { + /* get the coding scheme */ + i = n = msget1(ptr); + if(n < 0 || n > 39) { + mdvi_warning(_("%s: font coding scheme truncated to 40 bytes\n"), + filename); + n = 39; + } + memcpy(info->coding, ptr, n); + info->coding[n] = 0; + ptr += i; + } else + strcpy(info->coding, "FontSpecific"); + /* get the font family */ + if(lh > 12) { + n = msget1(ptr); + if(n > 0) { + i = Max(n, 63); + memcpy(info->family, ptr, i); + info->family[i] = 0; + } else + strcpy(info->family, "unspecified"); + ptr += n; + } + /* now we don't read from `ptr' anymore */ + + info->loc = bc; + info->hic = ec; + info->type = DviFontTFM; + + /* allocate characters */ + info->chars = xnalloc(TFMChar, size); + + +#ifdef WORD_LITTLE_ENDIAN + /* byte-swap the three arrays at once (they are consecutive in memory) */ + swap_array((Uint32 *)widths, nw + nh + nd); +#endif + + /* get the relevant data */ + ptr = (Uchar *)charinfo; + for(i = bc; i <= ec; ptr += 3, i++) { + int ndx; + + ndx = (int)*ptr; ptr++; + info->chars[i-bc].advance = widths[ndx]; + /* TFM files lack this information */ + info->chars[i-bc].left = 0; + info->chars[i-bc].right = widths[ndx]; + info->chars[i-bc].present = (ndx != 0); + if(ndx) { + ndx = ((*ptr >> 4) & 0xf); + info->chars[i-bc].height = heights[ndx]; + ndx = (*ptr & 0xf); + info->chars[i-bc].depth = depths[ndx]; + } + } + + /* free everything */ + mdvi_free(tfm); + + return 0; + +bad_tfm: + mdvi_error(_("%s: File corrupted, or not a TFM file\n"), filename); +error: + if(tfm) mdvi_free(tfm); + if(in) fclose(in); + return -1; +} + +static int ofm1_load_file(FILE *in, TFMInfo *info) +{ + int lf, lh, bc, ec, nw, nh, nd; + int nco, ncw, npc; + int i; + int n; + int size; + Int32 *tfm; + Int32 *widths; + Int32 *heights; + Int32 *depths; + TFMChar *tch; + TFMChar *end; + + lf = fuget4(in); + lh = fuget4(in); + bc = fuget4(in); + ec = fuget4(in); + nw = fuget4(in); + nh = fuget4(in); + nd = fuget4(in); + fuget4(in); /* italics */ + fuget4(in); /* lig-kern */ + fuget4(in); /* kern */ + fuget4(in); /* extensible recipe */ + fuget4(in); /* parameters */ + fuget4(in); /* direction */ + nco = fuget4(in); + ncw = fuget4(in); + npc = fuget4(in); + + /* get the checksum */ + info->checksum = fuget4(in); + /* the design size */ + info->design = fuget4(in); + /* get the coding scheme */ + if(lh > 2) { + /* get the coding scheme */ + i = n = fsget1(in); + if(n < 0 || n > 39) + n = 39; + fread(info->coding, 39, 1, in); + info->coding[n] = 0; + } else + strcpy(info->coding, "FontSpecific"); + /* get the font family */ + if(lh > 12) { + n = fsget1(in); + if(n > 0) { + i = Max(n, 63); + fread(info->family, i, 1, in); + info->family[i] = 0; + } else + strcpy(info->family, "unspecified"); + } + tfm = NULL; + + /* jump to the beginning of the char-info table */ + fseek(in, 4L*nco, SEEK_SET); + + size = ec - bc + 1; + info->loc = bc; + info->hic = ec; + info->chars = xnalloc(TFMChar, size); + end = info->chars + size; + + for(tch = info->chars, i = 0; i < ncw; i++) { + TFMChar ch; + int nr; + + /* in the characters we store the actual indices */ + ch.advance = fuget2(in); + ch.height = fuget1(in); + ch.depth = fuget1(in); + /* skip 2nd word */ + fuget4(in); + /* get # of repeats */ + nr = fuget2(in); + /* skip parameters */ + fseek(in, (long)npc * 2, SEEK_CUR); + /* if npc is odd, skip padding */ + if(npc & 1) fuget2(in); + + /* now repeat the character */ + while(nr-- >= 0 && tch < end) + memcpy(tch++, &ch, sizeof(TFMChar)); + if(tch == end) + goto bad_tfm; + } + + /* I wish we were done, but we aren't */ + + /* get the widths, heights and depths */ + size = nw + nh + nd; + tfm = xnalloc(Int32, size); + /* read them in one sweep */ + if(fread(tfm, 4, size, in) != size) { + mdvi_free(tfm); + goto bad_tfm; + } + + /* byte-swap things if necessary */ +#ifdef WORD_LITTLE_ENDIAN + swap_array((Uint32 *)tfm, size); +#endif + widths = tfm; + heights = widths + nw; + depths = heights + nh; + + if(widths[0] || heights[0] || depths[0]) + goto bad_tfm; + + /* now fix the characters */ + size = ec - bc + 1; + for(tch = info->chars; tch < end; tch++) { + tch->present = (tch->advance != 0); + tch->advance = widths[tch->advance]; + tch->height = heights[tch->height]; + tch->depth = depths[tch->depth]; + tch->left = 0; + tch->right = tch->advance; + } + + /* NOW we're done */ + mdvi_free(tfm); + return 0; + +bad_tfm: + if(tfm) mdvi_free(tfm); + return -1; +} + +/* we don't read OFM files into memory, because they can potentially be large */ +static int ofm_load_file(const char *filename, TFMInfo *info) +{ + int lf, lh, bc, ec, nw, nh, nd; + int i, n; + Int32 *tfm; + Uchar *ptr; + int size; + FILE *in; + Int32 *cb; + Int32 *charinfo; + Int32 *widths; + Int32 *heights; + Int32 *depths; + Uint32 checksum; + int olevel; + int nwords; + + in = fopen(filename, "rb"); + if(in == NULL) + return -1; + + /* not a checksum, but serves a similar purpose */ + checksum = 0; + + /* get the counters */ + /* get file level */ + olevel = fsget2(in); + if(olevel != 0) + goto bad_tfm; + olevel = fsget2(in); + if(olevel != 0) { + DEBUG((DBG_FONTS, "(mt) reading Level-1 OFM file `%s'\n", + filename)); + /* we handle level-1 files separately */ + if(ofm1_load_file(in, info) < 0) + goto bad_tfm; + return 0; + } + + DEBUG((DBG_FONTS, "(mt) reading Level-0 OFM file `%s'\n", filename)); + nwords = 14; + lf = fuget4(in); checksum = nwords; + lh = fuget4(in); checksum += lh; + bc = fuget4(in); + ec = fuget4(in); checksum += 2 * (ec - bc + 1); + nw = fuget4(in); checksum += nw; + nh = fuget4(in); checksum += nh; + nd = fuget4(in); checksum += nd; + checksum += fuget4(in); /* skip italics correction count */ + checksum += 2*fuget4(in); /* skip lig/kern table size */ + checksum += fuget4(in); /* skip kern table size */ + checksum += 2*fuget4(in); /* skip extensible recipe count */ + checksum += fuget4(in); /* skip # of font parameters */ + + /* I have found several .ofm files that seem to have the + * font-direction word missing, so we try to detect that here */ + if(checksum == lf + 1) { + DEBUG((DBG_FONTS, "(mt) font direction missing in `%s'\n", + filename)); + checksum--; + nwords--; + } else { + /* skip font direction */ + fuget4(in); + } + + if(checksum != lf || bc > ec + 1 || ec > 65535) + goto bad_tfm; + + /* now we're at the header */ + + /* get the checksum */ + info->checksum = fuget4(in); + /* get the design size */ + info->design = fuget4(in); + + /* get the coding scheme */ + if(lh > 2) { + /* get the coding scheme */ + i = n = fsget1(in); + if(n < 0 || n > 39) { + mdvi_warning(_("%s: font coding scheme truncated to 40 bytes\n"), + filename); + n = 39; + } + fread(info->coding, 39, 1, in); + info->coding[n] = 0; + } else + strcpy(info->coding, "FontSpecific"); + /* get the font family */ + if(lh > 12) { + n = fsget1(in); + if(n > 0) { + i = Max(n, 63); + fread(info->family, i, 1, in); + info->family[i] = 0; + } else + strcpy(info->family, "unspecified"); + } + + /* now skip anything else in the header */ + fseek(in, 4L*(nwords + lh), SEEK_SET); + /* and read everything at once */ + size = 2*(ec - bc + 1) + nw + nh + nd; + tfm = xnalloc(Int32, size * sizeof(Int32)); + if(fread(tfm, 4, size, in) != size) { + mdvi_free(tfm); + goto bad_tfm; + } + /* byte-swap all the tables at once */ +#ifdef WORD_LITTLE_ENDIAN + swap_array((Uint32 *)tfm, size); +#endif + cb = tfm; + charinfo = cb; cb += 2*(ec - bc + 1); + widths = cb; cb += nw; + heights = cb; cb += nh; + depths = cb; + + if(widths[0] || heights[0] || depths[0]) { + mdvi_free(tfm); + goto bad_tfm; + } + + /* from this point on, no error checking is done */ + + /* we don't need this anymore */ + fclose(in); + + /* now we don't read from `ptr' anymore */ + + info->loc = bc; + info->hic = ec; + info->type = DviFontTFM; + + /* allocate characters */ + info->chars = xnalloc(TFMChar, size); + + /* get the relevant data */ + ptr = (Uchar *)charinfo; + for(i = bc; i <= ec; ptr += 4, i++) { + int ndx; + + ndx = muget2(ptr); + info->chars[i-bc].advance = widths[ndx]; + /* TFM files lack this information */ + info->chars[i-bc].left = 0; + info->chars[i-bc].right = widths[ndx]; + info->chars[i-bc].present = (ndx != 0); + ndx = muget1(ptr); + info->chars[i-bc].height = heights[ndx]; + ndx = muget1(ptr); + info->chars[i-bc].depth = depths[ndx]; + } + + mdvi_free(tfm); + return 0; + +bad_tfm: + mdvi_error(_("%s: File corrupted, or not a TFM file\n"), filename); + fclose(in); + return -1; +} + +char *lookup_font_metrics(const char *name, int *type) +{ + char *file; + + switch(*type) { +#ifndef WITH_AFM_FILES + case DviFontAny: +#endif + case DviFontTFM: + file = kpse_find_tfm(name); + *type = DviFontTFM; + break; + case DviFontOFM: { + file = kpse_find_ofm(name); + /* we may have gotten a TFM back */ + if(file != NULL) { + const char *ext = file_extension(file); + if(ext && STREQ(ext, "tfm")) + *type = DviFontTFM; + } + break; + } +#ifdef WITH_AFM_FILES + case DviFontAFM: + file = kpse_find_file(name, kpse_afm_format, 0); + break; + case DviFontAny: + file = kpse_find_file(name, kpse_afm_format, 0); + *type = DviFontAFM; + if(file == NULL) { + file = kpse_find_tfm(name); + *type = DviFontTFM; + } + break; +#endif + default: + return NULL; + } + + return file; +} + +/* + * The next two functions are just wrappers for the font metric loaders, + * and use the pool of TFM data + */ + +/* this is how we interpret arguments: + * - if filename is NULL, we look for files of the given type, + * unless type is DviFontAny, in which case we try all the + * types we know of. + * - if filename is not NULL, we look at `type' to decide + * how to read the file. If type is DviFontAny, we just + * return an error. + */ +TFMInfo *get_font_metrics(const char *short_name, int type, const char *filename) +{ + TFMPool *tfm = NULL; + int status; + char *file; + + if(tfmpool.count) { + tfm = (TFMPool *)mdvi_hash_lookup(&tfmhash, + MDVI_KEY(short_name)); + if(tfm != NULL) { + DEBUG((DBG_FONTS, "(mt) reusing metric file `%s' (%d links)\n", + short_name, tfm->links)); + tfm->links++; + return &tfm->tfminfo; + } + } + + file = filename ? (char *)filename : lookup_font_metrics(short_name, &type); + if(file == NULL) + return NULL; + + tfm = xalloc(TFMPool); + DEBUG((DBG_FONTS, "(mt) loading font metric data from `%s'\n", file, file)); + switch(type) { + case DviFontTFM: + status = tfm_load_file(file, &tfm->tfminfo); + break; + case DviFontOFM: + status = ofm_load_file(file, &tfm->tfminfo); + break; +#ifdef WITH_AFM_FILES + case DviFontAFM: + status = afm_load_file(file, &tfm->tfminfo); + break; +#endif + default: + status = -1; + break; + } + if(file != filename) + mdvi_free(file); + if(status < 0) { + mdvi_free(tfm); + return NULL; + } + tfm->short_name = mdvi_strdup(short_name); + + /* add it to the pool */ + if(tfmpool.count == 0) + mdvi_hash_create(&tfmhash, TFM_HASH_SIZE); + mdvi_hash_add(&tfmhash, MDVI_KEY(tfm->short_name), + tfm, MDVI_HASH_UNCHECKED); + listh_prepend(&tfmpool, LIST(tfm)); + tfm->links = 1; + + return &tfm->tfminfo; +} + +void free_font_metrics(TFMInfo *info) +{ + TFMPool *tfm; + + if(tfmpool.count == 0) + return; + /* get the entry -- can't use the hash table for this, because + * we don't have the short name */ + for(tfm = (TFMPool *)tfmpool.head; tfm; tfm = tfm->next) + if(info == &tfm->tfminfo) + break; + if(tfm == NULL) + return; + if(--tfm->links > 0) { + DEBUG((DBG_FONTS, "(mt) %s not removed, still in use\n", + tfm->short_name)); + return; + } + mdvi_hash_remove_ptr(&tfmhash, MDVI_KEY(tfm->short_name)); + + DEBUG((DBG_FONTS, "(mt) removing unused TFM data for `%s'\n", tfm->short_name)); + listh_remove(&tfmpool, LIST(tfm)); + mdvi_free(tfm->short_name); + mdvi_free(tfm->tfminfo.chars); + mdvi_free(tfm); +} + +void flush_font_metrics(void) +{ + TFMPool *ptr; + + for(; (ptr = (TFMPool *)tfmpool.head); ) { + tfmpool.head = LIST(ptr->next); + + mdvi_free(ptr->short_name); + mdvi_free(ptr->tfminfo.chars); + mdvi_free(ptr); + } + mdvi_hash_reset(&tfmhash, 0); +} diff --git a/backend/dvi/mdvi-lib/tt.c b/backend/dvi/mdvi-lib/tt.c new file mode 100644 index 00000000..e85d8e70 --- /dev/null +++ b/backend/dvi/mdvi-lib/tt.c @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "mdvi.h" + +#ifdef WITH_TRUETYPE_FONTS + +#include +#include +#include +#include + +#include "private.h" + +static TT_Engine tt_handle; +static int initialized = 0; + +typedef struct ftinfo { + struct ftinfo *next; + struct ftinfo *prev; + char *fontname; + char *fmfname; + TT_Face face; + TT_Instance instance; + TT_Glyph glyph; + int hasmetrics; + int loaded; + int fmftype; + TFMInfo *tfminfo; + DviFontMapInfo mapinfo; + DviEncoding *encoding; +} FTInfo; + +static int tt_load_font __PROTO((DviParams *, DviFont *)); +static int tt_font_get_glyph __PROTO((DviParams *, DviFont *, int)); +static void tt_free_data __PROTO((DviFont *)); +static void tt_reset_font __PROTO((DviFont *)); +static void tt_shrink_glyph + __PROTO((DviContext *, DviFont *, DviFontChar *, DviGlyph *)); +static void tt_font_remove __PROTO((FTInfo *)); + +DviFontInfo tt_font_info = { + "TT", + 0, + tt_load_font, + tt_font_get_glyph, + tt_shrink_glyph, + mdvi_shrink_glyph_grey, + tt_free_data, /* free */ + tt_reset_font, /* reset */ + NULL, /* lookup */ + kpse_truetype_format, + NULL +}; + +#define FT_HASH_SIZE 31 + +static ListHead ttfonts = {NULL, NULL, 0}; + +static int init_freetype(void) +{ + TT_Error code; + + ASSERT(initialized == 0); + code = TT_Init_FreeType(&tt_handle); + if(code) { + DEBUG((DBG_TT, "(tt) Init_Freetype: error %d\n", code)); + return -1; + } + code = TT_Init_Post_Extension(tt_handle); + if(code) { + TT_Done_FreeType(tt_handle); + return -1; + } + /* we're on */ + initialized = 1; + return 0; +} + +static void tt_encode_font(DviFont *font, FTInfo *info) +{ + TT_Face_Properties prop; + int i; + + if(TT_Get_Face_Properties(info->face, &prop)) + return; + + for(i = 0; i < prop.num_Glyphs; i++) { + char *string; + int ndx; + + if(TT_Get_PS_Name(info->face, i, &string)) + continue; + ndx = mdvi_encode_glyph(info->encoding, string); + if(ndx < font->loc || ndx > font->hic) + continue; + font->chars[ndx - font->loc].code = i; + } +} + +static int tt_really_load_font(DviParams *params, DviFont *font, FTInfo *info) +{ + DviFontChar *ch; + TFMChar *ptr; + Int32 z, alpha, beta; + int i; + FTInfo *old; + TT_Error status; + double point_size; + static int warned = 0; + TT_CharMap cmap; + TT_Face_Properties props; + int map_found; + + DEBUG((DBG_TT, "(tt) really_load_font(%s)\n", info->fontname)); + + /* get the point size */ + point_size = (double)font->scale / (params->tfm_conv * 0x100000); + point_size = 72.0 * point_size / 72.27; + if(info->loaded) { + /* just reset the size info */ + TT_Set_Instance_Resolutions(info->instance, + params->dpi, params->vdpi); + TT_Set_Instance_CharSize(info->instance, FROUND(point_size * 64)); + /* FIXME: should extend/slant again */ + info->hasmetrics = 1; + return 0; + } + + /* load the face */ + DEBUG((DBG_TT, "(tt) loading new face `%s'\n", + info->fontname)); + status = TT_Open_Face(tt_handle, font->filename, &info->face); + if(status) { + mdvi_warning(_("(tt) %s: could not load face: %s\n"), + info->fontname, TT_ErrToString18(status)); + return -1; + } + + /* create a new instance of this face */ + status = TT_New_Instance(info->face, &info->instance); + if(status) { + mdvi_warning(_("(tt) %s: could not create face: %s\n"), + info->fontname, TT_ErrToString18(status)); + TT_Close_Face(info->face); + return -1; + } + + /* create a glyph */ + status = TT_New_Glyph(info->face, &info->glyph); + if(status) { + mdvi_warning(_("(tt) %s: could not create glyph: %s\n"), + info->fontname, TT_ErrToString18(status)); + goto tt_error; + } + + /* + * We'll try to find a Unicode charmap. It's not that important that we + * actually find one, especially if the fontmap files are installed + * properly, but it's good to have some predefined behaviour + */ + TT_Get_Face_Properties(info->face, &props); + + map_found = -1; + for(i = 0; map_found < 0 && i < props.num_CharMaps; i++) { + TT_UShort pid, eid; + + TT_Get_CharMap_ID(info->face, i, &pid, &eid); + switch(pid) { + case TT_PLATFORM_APPLE_UNICODE: + map_found = i; + break; + case TT_PLATFORM_ISO: + if(eid == TT_ISO_ID_7BIT_ASCII || + eid == TT_ISO_ID_8859_1) + map_found = 1; + break; + case TT_PLATFORM_MICROSOFT: + if(eid == TT_MS_ID_UNICODE_CS) + map_found = 1; + break; + } + } + if(map_found < 0) { + mdvi_warning(_("(tt) %s: no acceptable map found, using #0\n"), + info->fontname); + map_found = 0; + } + DEBUG((DBG_TT, "(tt) %s: using charmap #%d\n", + info->fontname, map_found)); + TT_Get_CharMap(info->face, map_found, &cmap); + + DEBUG((DBG_TT, "(tt) %s: Set_Char_Size(%.2f, %d, %d)\n", + font->fontname, point_size, font->hdpi, font->vdpi)); + status = TT_Set_Instance_Resolutions(info->instance, + params->dpi, params->vdpi); + if(status) { + error(_("(tt) %s: could not set resolution: %s\n"), + info->fontname, TT_ErrToString18(status)); + goto tt_error; + } + status = TT_Set_Instance_CharSize(info->instance, + FROUND(point_size * 64)); + if(status) { + error(_("(tt) %s: could not set point size: %s\n"), + info->fontname, TT_ErrToString18(status)); + goto tt_error; + } + + /* after this point we don't fail */ + + /* get information from the fontmap */ + status = mdvi_query_fontmap(&info->mapinfo, info->fontname); + if(!status && info->mapinfo.encoding) + info->encoding = mdvi_request_encoding(info->mapinfo.encoding); + else + info->encoding = NULL; + + if(info->encoding != NULL) { + TT_Post post; + + status = TT_Load_PS_Names(info->face, &post); + if(status) { + mdvi_warning(_("(tt) %s: could not load PS name table\n"), + info->fontname); + mdvi_release_encoding(info->encoding, 0); + info->encoding = NULL; + } + } + + /* get the metrics. If this fails, it's not fatal, but certainly bad */ + info->tfminfo = get_font_metrics(info->fontname, + info->fmftype, info->fmfname); + + if(info->tfminfo == NULL) { + mdvi_warning("(tt) %s: no metrics data, font ignored\n", + info->fontname); + goto tt_error; + } + /* fix this */ + font->design = info->tfminfo->design; + + /* get the scaled character metrics */ + get_tfm_chars(params, font, info->tfminfo, 0); + + if(info->encoding) + tt_encode_font(font, info); + else { + mdvi_warning(_("%s: no encoding vector found, expect bad output\n"), + info->fontname); + /* this is better than nothing */ + for(i = font->loc; i <= font->hic; i++) + font->chars[i - font->loc].code = TT_Char_Index(cmap, i); + } + + info->loaded = 1; + info->hasmetrics = 1; + return 0; + +tt_error: + tt_font_remove(info); + mdvi_free(font->chars); + font->chars = NULL; + font->loc = font->hic = 0; + return -1; +} + +static int tt_load_font(DviParams *params, DviFont *font) +{ + int i; + FTInfo *info; + + if(!initialized && init_freetype() < 0) + return -1; + + if(font->in != NULL) { + fclose(font->in); + font->in = NULL; + } + + info = xalloc(FTInfo); + + memzero(info, sizeof(FTInfo)); + info->fmftype = DviFontAny; /* any metrics type will do */ + info->fmfname = lookup_font_metrics(font->fontname, &info->fmftype); + info->fontname = font->fontname; + info->hasmetrics = 0; + info->loaded = 0; + + /* these will be obtained from the fontmaps */ + info->mapinfo.psname = NULL; + info->mapinfo.encoding = NULL; + info->mapinfo.fontfile = NULL; + info->mapinfo.extend = 0; + info->mapinfo.slant = 0; + + /* initialize these */ + font->chars = xnalloc(DviFontChar, 256); + font->loc = 0; + font->hic = 255; + for(i = 0; i < 256; i++) { + font->chars[i].offset = 1; + font->chars[i].glyph.data = NULL; + font->chars[i].shrunk.data = NULL; + font->chars[i].grey.data = NULL; + } + + if(info->fmfname == NULL) + mdvi_warning(_("(tt) %s: no font metric data\n"), font->fontname); + + listh_append(&ttfonts, LIST(info)); + font->private = info; + + return 0; +} + +static int tt_get_bitmap(DviParams *params, DviFont *font, + int code, double xscale, double yscale, DviGlyph *glyph) +{ + TT_Outline outline; + TT_Raster_Map raster; + TT_BBox bbox; + TT_Glyph_Metrics metrics; + TT_Matrix mat; + FTInfo *info; + int error; + int have_outline = 0; + int w, h; + + info = (FTInfo *)font->private; + if(info == NULL) + return -1; + + error = TT_Load_Glyph(info->instance, info->glyph, + code, TTLOAD_DEFAULT); + if(error) goto tt_error; + error = TT_Get_Glyph_Outline(info->glyph, &outline); + if(error) goto tt_error; + have_outline = 1; + mat.xx = FROUND(xscale * 65536); + mat.yy = FROUND(yscale * 65536); + mat.yx = 0; + mat.xy = 0; + TT_Transform_Outline(&outline, &mat); + error = TT_Get_Outline_BBox(&outline, &bbox); + if(error) goto tt_error; + bbox.xMin &= -64; + bbox.yMin &= -64; + bbox.xMax = (bbox.xMax + 63) & -64; + bbox.yMax = (bbox.yMax + 63) & -64; + w = (bbox.xMax - bbox.xMin) / 64; + h = (bbox.yMax - bbox.yMin) / 64; + + glyph->w = w; + glyph->h = h; + glyph->x = -bbox.xMin / 64; + glyph->y = bbox.yMax / 64; + if(!w || !h) + goto tt_error; + raster.rows = h; + raster.width = w; + raster.cols = ROUND(w, 8); + raster.size = h * raster.cols; + raster.flow = TT_Flow_Down; + raster.bitmap = mdvi_calloc(h, raster.cols); + + TT_Translate_Outline(&outline, -bbox.xMin, -bbox.yMin); + TT_Get_Outline_Bitmap(tt_handle, &outline, &raster); + glyph->data = bitmap_convert_msb8(raster.bitmap, w, h, ROUND(w, 8)); + TT_Done_Outline(&outline); + mdvi_free(raster.bitmap); + + return 0; +tt_error: + if(have_outline) + TT_Done_Outline(&outline); + return -1; +} + +static int tt_font_get_glyph(DviParams *params, DviFont *font, int code) +{ + FTInfo *info = (FTInfo *)font->private; + DviFontChar *ch; + int error; + double xs, ys; + int dpi; + + ASSERT(info != NULL); + if(!info->hasmetrics && tt_really_load_font(params, font, info) < 0) + return -1; + ch = FONTCHAR(font, code); + if(!ch || !glyph_present(ch)) + return -1; + ch->loaded = 1; + if(!ch->width || !ch->height) + goto blank; + if(ch->code == 0) { + ch->glyph.data = NULL; + goto missing; + } + /* get the glyph */ + dpi = Max(font->hdpi, font->vdpi); + error = tt_get_bitmap(params, font, ch->code, + (double)font->hdpi / dpi, + (double)font->vdpi / dpi, + &ch->glyph); + if(error) + goto missing; + ch->x = ch->glyph.x; + ch->y = ch->glyph.y; + + return 0; + +missing: + ch->glyph.data = MDVI_GLYPH_EMPTY; + ch->missing = 1; +blank: + ch->glyph.w = ch->width; + ch->glyph.h = ch->height; + ch->glyph.x = ch->x; + ch->glyph.y = ch->y; + return 0; +} + +static void tt_shrink_glyph(DviContext *dvi, DviFont *font, DviFontChar *ch, DviGlyph *dest) +{ + tt_get_bitmap(&dvi->params, font, + ch->code, + (double)font->hdpi / (dvi->params.dpi * dvi->params.hshrink), + (double)font->vdpi / (dvi->params.vdpi * dvi->params.vshrink), + dest); + /* transform the glyph for the current orientation */ + font_transform_glyph(dvi->params.orientation, dest); +} + +static void tt_reset_font(DviFont *font) +{ + FTInfo *info = (FTInfo *)font->private; + + if(info == NULL) + return; + info->hasmetrics = 0; +} + +static void tt_font_remove(FTInfo *info) +{ + FTInfo *old; + + if(info->loaded) { + /* all fonts in the hash table have called TT_Open_Face */ + TT_Done_Instance(info->instance); + TT_Close_Face(info->face); + } + listh_remove(&ttfonts, LIST(info)); + /* release our encodings */ + if(info->encoding) + mdvi_release_encoding(info->encoding, 1); + /* and destroy the font */ + if(info->tfminfo) + free_font_metrics(info->tfminfo); + if(info->fmfname) + mdvi_free(info->fmfname); + mdvi_free(info); +} + +static void tt_free_data(DviFont *font) +{ + if(font->private == NULL) + return; + + tt_font_remove((FTInfo *)font->private); + if(initialized && ttfonts.count == 0) { + DEBUG((DBG_TT, "(tt) last font removed -- closing FreeType\n")); + TT_Done_FreeType(tt_handle); + initialized = 0; + } +} + +#endif /* WITH_TRUETYPE_FONTS */ diff --git a/backend/dvi/mdvi-lib/util.c b/backend/dvi/mdvi-lib/util.c new file mode 100644 index 00000000..349d273a --- /dev/null +++ b/backend/dvi/mdvi-lib/util.c @@ -0,0 +1,550 @@ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "private.h" + +static char *const messages[] = { + _G("Ooops!"), + _G("Aieeeee!!"), + _G("Ouch!"), + _G("Houston, we have a problem"), + _G("3.. 2.. 1.. BOOM!"), + _G("I'm history"), + _G("I'm going down"), + _G("I smell a rat") +}; +#define NMSGS (sizeof(messages) / sizeof(char *)) + +static FILE *logfile = NULL; +static int _mdvi_log_level; + +int mdvi_set_logfile(const char *filename); +int mdvi_set_logstream(FILE *file); +int mdvi_set_loglevel(int level); + +static void vputlog(int level, const char *head, const char *format, va_list ap) +{ + if(logfile != NULL && _mdvi_log_level >= level) { + if(head != NULL) + fprintf(logfile, "%s: ", head); + vfprintf(logfile, format, ap); + } +} + +int mdvi_set_logfile(const char *filename) +{ + FILE *f = NULL; + + if(filename && (f = fopen(filename, "w")) == NULL) + return -1; + if(logfile != NULL && !isatty(fileno(logfile))) { + fclose(logfile); + logfile = NULL; + } + if(filename) + logfile = f; + return 0; +} + +int mdvi_set_logstream(FILE *file) +{ + if(logfile && !isatty(fileno(logfile))) { + fclose(logfile); + logfile = NULL; + } + logfile = file; + return 0; +} + +int mdvi_set_loglevel(int level) +{ + int old = _mdvi_log_level; + + _mdvi_log_level = level; + return old; +} + +#ifndef NODEBUG +Uint32 _mdvi_debug_mask = 0; + +void __debug(int mask, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + if(_mdvi_debug_mask & mask) { + if(!DEBUGGING(SILENT)) { + fprintf(stderr, "Debug: "); + vfprintf(stderr, format, ap); + fflush(stderr); + } +#ifndef __GNUC__ + /* let's be portable */ + va_end(ap); + va_start(ap, format); +#endif + vputlog(LOG_DEBUG, "Debug", format, ap); + } + va_end(ap); +} +#endif + +void mdvi_message(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + if(_mdvi_log_level >= LOG_INFO) { + fprintf(stderr, "%s: ", program_name); + vfprintf(stderr, format, ap); +#ifndef __GNUC__ + va_end(ap); + va_start(ap, format); +#endif + } + vputlog(LOG_INFO, NULL, format, ap); + va_end(ap); +} + +void mdvi_crash(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + fprintf(stderr, "%s: %s: ", + program_name, + gettext(messages[(int)time(NULL) % NMSGS])); + vfprintf(stderr, format, ap); +#ifndef __GNUC__ + /* let's be portable */ + va_end(ap); + va_start(ap, format); +#endif + vputlog(LOG_ERROR, _("Crashing"), format, ap); + va_end(ap); + abort(); +} + +void mdvi_error(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + fprintf(stderr, _("%s: Error: "), program_name); + vfprintf(stderr, format, ap); +#ifndef __GNUC__ + /* let's be portable */ + va_end(ap); + va_start(ap, format); +#endif + vputlog(LOG_ERROR, _("Error"), format, ap); + va_end(ap); +} + +void mdvi_warning(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + fprintf(stderr, _("%s: Warning: "), program_name); + vfprintf(stderr, format, ap); +#ifndef __GNUC__ + /* let's be portable */ + va_end(ap); + va_start(ap, format); +#endif + vputlog(LOG_WARN, _("Warning"), format, ap); + va_end(ap); +} + +void mdvi_fatal(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + fprintf(stderr, _("%s: Fatal: "), program_name); + vfprintf(stderr, format, ap); +#ifndef __GNUC__ + /* let's be portable */ + va_end(ap); + va_start(ap, format); +#endif + vputlog(LOG_ERROR, _("Fatal"), format, ap); + va_end(ap); +#ifndef NODEBUG + abort(); +#else + exit(EXIT_FAILURE); +#endif +} + +void *mdvi_malloc(size_t nelems) +{ + void *ptr = malloc(nelems); + + if(ptr == NULL) + mdvi_fatal(_("out of memory allocating %u bytes\n"), + (unsigned)nelems); + return ptr; +} + +void *mdvi_realloc(void *data, size_t newsize) +{ + void *ptr; + + if(newsize == 0) + mdvi_crash(_("attempted to reallocate with zero size\n")); + ptr = realloc(data, newsize); + if(ptr == NULL) + mdvi_fatal(_("failed to reallocate %u bytes\n"), (unsigned)newsize); + return ptr; +} + +void *mdvi_calloc(size_t nmemb, size_t size) +{ + void *ptr; + + if(nmemb == 0) + mdvi_crash(_("attempted to callocate 0 members\n")); + if(size == 0) + mdvi_crash(_("attempted to callocate %u members with size 0\n"), + (unsigned)nmemb); + ptr = calloc(nmemb, size); + if(ptr == 0) + mdvi_fatal(_("failed to allocate %ux%u bytes\n"), + (unsigned)nmemb, (unsigned)size); + return ptr; +} + +void mdvi_free(void *ptr) +{ + if(ptr == NULL) + mdvi_crash(_("attempted to free NULL pointer\n")); + free(ptr); +} + +char *mdvi_strdup(const char *string) +{ + int length; + char *ptr; + + length = strlen(string) + 1; + ptr = (char *)mdvi_malloc(length); + memcpy(ptr, string, length); + return ptr; +} + +/* `to' should have room for length+1 bytes */ +char *mdvi_strncpy(char *to, const char *from, size_t length) +{ + strncpy(to, from, length); + to[length] = '\0'; + return to; +} + +char *mdvi_strndup(const char *string, size_t length) +{ + int n; + char *ptr; + + n = strlen(string); + if(n > length) + n = length; + ptr = (char *)mdvi_malloc(n + 1); + memcpy(ptr, string, n); + return ptr; +} + +void *mdvi_memdup(const void *data, size_t length) +{ + void *ptr = mdvi_malloc(length); + + memcpy(ptr, data, length); + return ptr; +} + +char *mdvi_strrstr (const char *haystack, const char *needle) +{ + size_t i; + size_t needle_len; + size_t haystack_len; + const char *p; + + needle_len = strlen (needle); + haystack_len = strlen (haystack); + + if (needle_len == 0) + return NULL; + + if (haystack_len < needle_len) + return (char *)haystack; + + p = haystack + haystack_len - needle_len; + while (p >= haystack) { + for (i = 0; i < needle_len; i++) + if (p[i] != needle[i]) + goto next; + + return (char *)p; + + next: + p--; + } + + return NULL; +} + +char *mdvi_build_path_from_cwd (const char *path) +{ + char *ptr; + char *buf = NULL; + size_t buf_size = 512; + + while (1) { + buf = mdvi_realloc (buf, buf_size); + if ((ptr = getcwd (buf, buf_size)) == NULL && errno == ERANGE) { + buf_size *= 2; + } else { + buf = ptr; + break; + } + } + + buf = mdvi_realloc (buf, strlen (buf) + strlen (path) + 2); + strcat (buf, "/"); + strncat (buf, path, strlen (path)); + + return buf; +} + +double unit2pix_factor(const char *spec) +{ + double val; + double factor; + const char *p, *q; + static const char *units = "incmmmmtptpcddccspbpftydcs"; + + val = 0.0; + + for(p = spec; *p >= '0' && *p <= '9'; p++) + val = 10.0 * val + (double)(*p - '0'); + if(*p == '.') { + p++; + factor = 0.1; + while(*p && *p >= '0' && *p <= '9') { + val += (*p++ - '0') * factor; + factor = factor * 0.1; + } + } + factor = 1.0; + for(q = units; *q; q += 2) { + if(p[0] == q[0] && p[1] == q[1]) + break; + } + switch((int)(q - units)) { + /*in*/ case 0: factor = 1.0; break; + /*cm*/ case 2: factor = 1.0 / 2.54; break; + /*mm*/ case 4: factor = 1.0 / 25.4; break; + /*mt*/ case 6: factor = 1.0 / 0.0254; break; + /*pt*/ case 8: factor = 1.0 / 72.27; break; + /*pc*/ case 10: factor = 12.0 / 72.27; break; + /*dd*/ case 12: factor = (1238.0 / 1157.0) / 72.27; break; + /*cc*/ case 14: factor = 12 * (1238.0 / 1157.0) / 72.27; break; + /*sp*/ case 16: factor = 1.0 / (72.27 * 65536); break; + /*bp*/ case 18: factor = 1.0 / 72.0; break; + /*ft*/ case 20: factor = 12.0; break; + /*yd*/ case 22: factor = 36.0; break; + /*cs*/ case 24: factor = 1.0 / 72000.0; break; + default: factor = 1.0; + } + return factor * val; +} + +int unit2pix(int dpi, const char *spec) +{ + double factor = unit2pix_factor(spec); + + return (int)(factor * dpi + 0.5); +} + +Ulong get_mtime(int fd) +{ + struct stat st; + + if(fstat(fd, &st) == 0) + return (Ulong)st.st_mtime; + return 0; +} + +char *xstradd(char *dest, size_t *size, size_t n, const char *src, size_t m) +{ + if(m == 0) + m = strlen(src); + if(n + m >= *size) { + dest = mdvi_realloc(dest, n + m + 1); + *size = n + m + 1; + } + memcpy(dest + n, src, m); + dest[n + m] = 0; + return dest; +} + +char *getword(char *string, const char *delim, char **end) +{ + char *ptr; + char *word; + + /* skip leading delimiters */ + for(ptr = string; *ptr && strchr(delim, *ptr); ptr++); + + if(*ptr == 0) + return NULL; + word = ptr++; + /* skip non-delimiters */ + while(*ptr && !strchr(delim, *ptr)) + ptr++; + *end = (char *)ptr; + return word; +} + +char *getstring(char *string, const char *delim, char **end) +{ + char *ptr; + char *word; + int quoted = 0; + + /* skip leading delimiters */ + for(ptr = string; *ptr && strchr(delim, *ptr); ptr++); + + if(ptr == NULL) + return NULL; + quoted = (*ptr == '"'); + if(quoted) + for(word = ++ptr; *ptr && *ptr != '"'; ptr++); + else + for(word = ptr; *ptr && !strchr(delim, *ptr); ptr++); + *end = (char *)ptr; + return word; +} + +static long pow2(size_t n) +{ + long x = 8; /* don't bother allocating less than this */ + + while(x < n) + x <<= 1L; + return x; +} + +void dstring_init(Dstring *dstr) +{ + dstr->data = NULL; + dstr->size = 0; + dstr->length = 0; +} + +int dstring_append(Dstring *dstr, const char *string, int len) +{ + if(len < 0) + len = strlen(string); + if(len) { + if(dstr->length + len >= dstr->size) { + dstr->size = pow2(dstr->length + len + 1); + dstr->data = mdvi_realloc(dstr->data, dstr->size); + } + memcpy(dstr->data + dstr->length, string, len); + dstr->length += len; + dstr->data[dstr->length] = 0; + } else if(dstr->size == 0) { + ASSERT(dstr->data == NULL); + dstr->size = 8; + dstr->data = mdvi_malloc(8); + dstr->data[0] = 0; + } + + return dstr->length; +} + +int dstring_copy(Dstring *dstr, int pos, const char *string, int len) +{ + ASSERT(pos >= 0); + if(len < 0) + len = strlen(string); + if(len) { + if(pos + len >= dstr->length) { + dstr->length = pos; + return dstring_append(dstr, string, len); + } + memcpy(dstr->data + pos, string, len); + } + return dstr->length; +} + +int dstring_insert(Dstring *dstr, int pos, const char *string, int len) +{ + ASSERT(pos >= 0); + if(pos == dstr->length) + return dstring_append(dstr, string, len); + if(len < 0) + len = strlen(string); + if(len) { + if(dstr->length + len >= dstr->size) { + dstr->size = pow2(dstr->length + len + 1); + dstr->data = mdvi_realloc(dstr->data, dstr->size); + } + /* make room */ + memmove(dstr->data + pos, dstr->data + pos + len, len); + /* now copy */ + memcpy(dstr->data + pos, string, len); + dstr->length += len; + dstr->data[dstr->length] = 0; + } + return dstr->length; +} + +int dstring_new(Dstring *dstr, const char *string, int len) +{ + if(len < 0) + len = strlen(string); + if(len) { + dstr->size = pow2(len + 1); + dstr->data = mdvi_malloc(dstr->size * len); + memcpy(dstr->data, string, len); + } else + dstring_init(dstr); + return dstr->length; +} + +void dstring_reset(Dstring *dstr) +{ + if(dstr->data) + mdvi_free(dstr->data); + dstring_init(dstr); +} + diff --git a/backend/dvi/mdvi-lib/vf.c b/backend/dvi/mdvi-lib/vf.c new file mode 100644 index 00000000..fb498476 --- /dev/null +++ b/backend/dvi/mdvi-lib/vf.c @@ -0,0 +1,241 @@ +/* vf.c -- VF font support */ +/* + * Copyright (C) 2000, Matias Atria + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include "mdvi.h" +#include "private.h" + +static int vf_load_font __PROTO((DviParams *, DviFont *)); +static void vf_free_macros __PROTO((DviFont *)); + +/* only symbol exported by this file */ +DviFontInfo vf_font_info = { + "VF", + 1, /* virtual fonts scale just fine */ + vf_load_font, + NULL, /* get_glyph */ + NULL, /* shrink0 */ + NULL, /* shrink1 */ + vf_free_macros, + NULL, /* reset */ + NULL, /* lookup */ + kpse_vf_format, + NULL +}; + +DviFontInfo ovf_font_info = { + "OVF", + 1, /* virtual fonts scale just fine */ + vf_load_font, + NULL, /* get_glyph */ + NULL, /* shrink0 */ + NULL, /* shrink1 */ + vf_free_macros, + NULL, /* reset */ + NULL, /* lookup */ + kpse_ovf_format, + NULL +}; + +static int vf_load_font(DviParams *params, DviFont *font) +{ + FILE *p; + Uchar *macros; + int msize; + int mlen; + Int32 checksum; + long alpha, beta, z; + int op; + int i; + int nchars; + int loc, hic; + DviFontRef *last; + + macros = NULL; + msize = mlen = 0; + p = font->in; + + if(fuget1(p) != 247 || fuget1(p) != 202) + goto badvf; + mlen = fuget1(p); + fseek(p, (long)mlen, SEEK_CUR); + checksum = fuget4(p); + if(checksum && font->checksum && checksum != font->checksum) { + mdvi_warning(_("%s: Checksum mismatch (expected %u, got %u)\n"), + font->fontname, font->checksum, checksum); + } else if(!font->checksum) + font->checksum = checksum; + font->design = fuget4(p); + + /* read all the fonts in the preamble */ + last = NULL; + + /* initialize alpha, beta and z for TFM width computation */ + TFMPREPARE(font->scale, z, alpha, beta); + + op = fuget1(p); + while(op >= DVI_FNT_DEF1 && op <= DVI_FNT_DEF4) { + DviFontRef *ref; + Int32 scale, design; + Uint32 checksum; + int id; + int n; + int hdpi; + int vdpi; + char *name; + + /* process fnt_def commands */ + + id = fugetn(p, op - DVI_FNT_DEF1 + 1); + checksum = fuget4(p); + scale = fuget4(p); + design = fuget4(p); + + /* scale this font according to our parent's scale */ + scale = TFMSCALE(scale, z, alpha, beta); + design = FROUND(params->tfm_conv * design); + + /* compute the resolution */ + hdpi = FROUND(params->mag * params->dpi * scale / design); + vdpi = FROUND(params->mag * params->vdpi * scale / design); + n = fuget1(p) + fuget1(p); + name = mdvi_malloc(n + 1); + fread(name, 1, n, p); + name[n] = 0; + DEBUG((DBG_FONTS, "(vf) %s: defined font `%s' at %.1fpt (%dx%d dpi)\n", + font->fontname, name, + (double)scale / (params->tfm_conv * 0x100000), hdpi, vdpi)); + + /* get the font */ + ref = font_reference(params, id, name, checksum, hdpi, vdpi, scale); + if(ref == NULL) { + mdvi_error(_("(vf) %s: could not load font `%s'\n"), + font->fontname, name); + goto error; + } + mdvi_free(name); + if(last == NULL) + font->subfonts = last = ref; + else + last->next = ref; + ref->next = NULL; + op = fuget1(p); + } + + if(op >= DVI_FNT_DEF1 && op <= DVI_FNT_DEF4) + goto error; + + /* This function correctly reads both .vf and .ovf files */ + + font->chars = xnalloc(DviFontChar, 256); + for(i = 0; i < 256; i++) + font->chars[i].offset = 0; + nchars = 256; + loc = -1; hic = -1; + /* now read the characters themselves */ + while(op <= 242) { + int pl; + Int32 cc; + Int32 tfm; + + if(op == 242) { + pl = fuget4(p); + cc = fuget4(p); + tfm = fuget4(p); + } else { + pl = op; + cc = fuget1(p); + tfm = fuget3(p); + } + if(loc < 0 || cc < loc) + loc = cc; + if(hic < 0 || cc > hic) + hic = cc; + if(cc >= nchars) { + font->chars = xresize(font->chars, + DviFontChar, cc + 16); + for(i = nchars; i < cc + 16; i++) + font->chars[i].offset = 0; + nchars = cc + 16; + } + if(font->chars[cc].offset) { + mdvi_error(_("(vf) %s: character %d redefined\n"), + font->fontname, cc); + goto error; + } + + DEBUG((DBG_GLYPHS, "(vf) %s: defined character %d (macro length %d)\n", + font->fontname, cc, pl)); + font->chars[cc].width = pl + 1; + font->chars[cc].code = cc; + font->chars[cc].tfmwidth = TFMSCALE(tfm, z, alpha, beta); + font->chars[cc].offset = mlen; + font->chars[cc].loaded = 1; + if(mlen + pl + 1 > msize) { + msize = mlen + pl + 256; + macros = xresize(macros, Uchar, msize); + } + if(pl && fread(macros + mlen, 1, pl, p) != pl) + break; + macros[mlen+pl] = DVI_EOP; + mlen += pl + 1; + op = fuget1(p); + } + if(op != 248) { + mdvi_error(_("(vf) %s: no postamble\n"), font->fontname); + goto error; + } + + /* make macro memory just big enough */ + if(msize > mlen) { + macros = xresize(macros, Uchar, mlen); + msize = mlen; + } + + DEBUG((DBG_FONTS|DBG_GLYPHS, + "(vf) %s: macros use %d bytes\n", font->fontname, msize)); + + if(loc > 0 || hic < nchars-1) { + memmove(font->chars, font->chars + loc, + (hic - loc + 1) * sizeof(DviFontChar)); + font->chars = xresize(font->chars, + DviFontChar, hic - loc + 1); + } + font->loc = loc; + font->hic = hic; + font->private = macros; + + return 0; + +badvf: + mdvi_error(_("%s: File corrupted, or not a VF file.\n"), font->fontname); +error: + if(font->chars) + mdvi_free(font->chars); + if(macros) + mdvi_free(macros); + return -1; +} + +static void vf_free_macros(DviFont *font) +{ + mdvi_free(font->private); +} diff --git a/backend/dvi/texmfcnf.c b/backend/dvi/texmfcnf.c new file mode 100644 index 00000000..9ca3125f --- /dev/null +++ b/backend/dvi/texmfcnf.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010, Hib Eris + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include "texmfcnf.h" + +#include +#include +#ifdef G_OS_WIN32 +#include +#endif + +gchar * +get_texmfcnf(void) +{ + char *env = getenv("TEXMFCNF"); + if (env) + return g_strdup(env); + +#ifdef G_OS_WIN32 + gchar *texmfcnf = NULL; + TCHAR path[_MAX_PATH]; + + if (SearchPath(NULL, "mktexpk", ".exe", _MAX_PATH, path, NULL)) + { + gchar *sdir, *sdir_parent, *sdir_grandparent; + const gchar *texmfcnf_fmt = "{%s,%s,%s}{,{/share,}/texmf{-local,}/web2c}"; + + sdir = g_path_get_dirname(path); + sdir_parent = g_path_get_dirname(sdir); + sdir_grandparent = g_path_get_dirname(sdir_parent); + + texmfcnf = g_strdup_printf(texmfcnf_fmt, + sdir, sdir_parent, sdir_grandparent); + + g_free(sdir); + g_free(sdir_parent); + g_free(sdir_grandparent); + } + return texmfcnf; +#else + return NULL; +#endif +} + + + + diff --git a/backend/dvi/texmfcnf.h b/backend/dvi/texmfcnf.h new file mode 100644 index 00000000..11cb9b3d --- /dev/null +++ b/backend/dvi/texmfcnf.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010, Hib Eris + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TEXMFCNF_H +#define TEXMFCNF_H + +#include + +G_BEGIN_DECLS + +gchar *get_texmfcnf(void); + +G_END_DECLS + +#endif /* TEXMFCNF_H */ + + + + diff --git a/backend/impress/Makefile.am b/backend/impress/Makefile.am new file mode 100644 index 00000000..a7b65705 --- /dev/null +++ b/backend/impress/Makefile.am @@ -0,0 +1,57 @@ +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/libdocument \ + -DMATELOCALEDIR=\"$(datadir)/locale\" \ + -DEVINCEDATADIR=\""$(datadir)"\" \ + -DEVINCE_COMPILATION \ + $(BACKEND_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED) + +backend_LTLIBRARIES = libimpressdocument.la + +libimpressdocument_la_SOURCES = \ + $(IMPOSTER_SOURCE_FILES) \ + $(IMPOSTER_INCLUDE_FILES) \ + impress-document.c \ + impress-document.h + +IMPOSTER_SOURCE_FILES = \ + document.c \ + f_oasis.c \ + f_oo13.c \ + iksemel.c \ + r_back.c \ + r_draw.c \ + render.c \ + r_geometry.c \ + r_gradient.c \ + r_style.c \ + r_text.c \ + zip.c +IMPOSTER_INCLUDE_FILES = \ + common.h \ + iksemel.h \ + imposter.h \ + internal.h \ + zip.h +IMPOSTER_RENDER_SOURCE_FILES = \ + render.c +IMPOSTER_RENDER_INCLUDE_FILES = \ + render.h + +libimpressdocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS) +libimpressdocument_la_LIBADD = \ + $(top_builddir)/libdocument/libevdocument.la \ + $(BACKEND_LIBS) -lz + +backend_in_files = impressdocument.evince-backend.in +backend_DATA = $(backend_in_files:.evince-backend.in=.evince-backend) + +EXTRA_DIST = $(backend_in_files) + +CLEANFILES = $(backend_DATA) + +@EV_INTLTOOL_EVINCE_BACKEND_RULE@ + +-include $(top_srcdir)/git.mk diff --git a/backend/impress/common.h b/backend/impress/common.h new file mode 100644 index 00000000..73e4ac19 --- /dev/null +++ b/backend/impress/common.h @@ -0,0 +1,40 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#ifndef COMMON_H +#define COMMON_H 1 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#ifdef STDC_HEADERS +#include +#include +#include +#elif HAVE_STRINGS_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifndef errno +extern int errno; +#endif + +#include +#include "imposter.h" + + +#endif /* COMMON_H */ diff --git a/backend/impress/document.c b/backend/impress/document.c new file mode 100644 index 00000000..b01c0e18 --- /dev/null +++ b/backend/impress/document.c @@ -0,0 +1,140 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "internal.h" + +static iks * +_imp_load_xml(ImpDoc *doc, const char *xmlfile) +{ + int e; + iks *x; + + x = zip_load_xml (doc->zfile, xmlfile, &e); + return x; +} + +ImpDoc * +imp_open(const char *filename, int *err) +{ + ImpDoc *doc; + int e; + + doc = calloc(1, sizeof(ImpDoc)); + if (!doc) { + *err = IMP_NOMEM; + return NULL; + } + + doc->stack = iks_stack_new(sizeof(ImpPage) * 32, 0); + if (!doc->stack) { + *err = IMP_NOMEM; + imp_close(doc); + return NULL; + } + + doc->zfile = zip_open(filename, &e); + if (e) { + *err = IMP_NOTZIP; + imp_close(doc); + return NULL; + } + + doc->content = _imp_load_xml(doc, "content.xml"); + doc->styles = _imp_load_xml(doc, "styles.xml"); + doc->meta = _imp_load_xml(doc, "meta.xml"); + + if (!doc->content || !doc->styles) { + *err = IMP_BADDOC; + imp_close(doc); + return NULL; + } + + e = _imp_oo13_load(doc); + if (e && e != IMP_NOTIMP) { + *err = e; + imp_close(doc); + return NULL; + } + + if (e == IMP_NOTIMP) { + e = _imp_oasis_load(doc); + if (e) { + *err = e; + imp_close(doc); + return NULL; + } + } + + return doc; +} + +int +imp_nr_pages(ImpDoc *doc) +{ + return doc->nr_pages; +} + +ImpPage * +imp_get_page(ImpDoc *doc, int page_no) +{ + if (page_no == IMP_LAST_PAGE) { + return doc->last_page; + } else { + ImpPage *page; + if (page_no < 0 || page_no > doc->nr_pages) return NULL; + for (page = doc->pages; page_no; --page_no) { + page = page->next; + } + return page; + } +} + +ImpPage * +imp_next_page(ImpPage *page) +{ + return page->next; +} + +ImpPage * +imp_prev_page(ImpPage *page) +{ + return page->prev; +} + +int +imp_get_page_no(ImpPage *page) +{ + return page->nr; +} + +const char * +imp_get_page_name(ImpPage *page) +{ + return page->name; +} + +void * +imp_get_xml(ImpDoc *doc, const char *filename) +{ + if (strcmp(filename, "content.xml") == 0) + return doc->content; + else if (strcmp(filename, "styles.xml") == 0) + return doc->styles; + else if (strcmp(filename, "meta.xml") == 0) + return doc->meta; + else + return NULL; +} + +void +imp_close(ImpDoc *doc) +{ + if (doc->stack) iks_stack_delete(doc->stack); + if (doc->zfile) zip_close(doc->zfile); + free(doc); +} diff --git a/backend/impress/f_oasis.c b/backend/impress/f_oasis.c new file mode 100644 index 00000000..8fe6ee1a --- /dev/null +++ b/backend/impress/f_oasis.c @@ -0,0 +1,170 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "internal.h" + +static void +render_object(ImpRenderCtx *ctx, void *drw_data, iks *node) +{ + char *tag, *t; + ImpColor fg; + + tag = iks_name(node); + if (strcmp(tag, "draw:g") == 0) { + iks *x; + for (x = iks_first_tag(node); x; x = iks_next_tag(x)) { + render_object(ctx, drw_data, x); + } + } else if (strcmp(tag, "draw:frame") == 0) { + iks *x; + for (x = iks_first_tag(node); x; x = iks_next_tag(x)) { + render_object(ctx, drw_data, x); + } + } else if (strcmp(tag, "draw:line") == 0) { + r_get_color(ctx, node, "svg:stroke-color", &fg); + ctx->drw->set_fg_color(drw_data, &fg); + ctx->drw->draw_line(drw_data, + r_get_x(ctx, node, "svg:x1"), r_get_y(ctx, node, "svg:y1"), + r_get_x(ctx, node, "svg:x2"), r_get_y(ctx, node, "svg:y2") + ); + } else if (strcmp(tag, "draw:rect") == 0) { + int x, y, w, h, r = 0; + char *t; + x = r_get_x(ctx, node, "svg:x"); + y = r_get_y(ctx, node, "svg:y"); + w = r_get_x(ctx, node, "svg:width"); + h = r_get_y(ctx, node, "svg:height"); + t = r_get_style(ctx, node, "draw:corner-radius"); + if (t) r = atof(t) * ctx->fact_x; + if (r_get_style(ctx, node, "draw:fill")) { + r_get_color(ctx, node, "draw:fill-color", &fg); + ctx->drw->set_fg_color(drw_data, &fg); + _imp_draw_rect(ctx, drw_data, 1, x, y, w, h, r); + } + r_get_color(ctx, node, "svg:stroke-color", &fg); + ctx->drw->set_fg_color(drw_data, &fg); + _imp_draw_rect(ctx, drw_data, 0, x, y, w, h, r); + r_text(ctx, drw_data, node); + } else if (strcmp(tag, "draw:ellipse") == 0 || strcmp(tag, "draw:circle") == 0) { + int sa, ea, fill = 0; + r_get_color(ctx, node, "svg:stroke-color", &fg); + sa = r_get_angle(node, "draw:start-angle", 0); + ea = r_get_angle(node, "draw:end-angle", 360); + if (ea > sa) ea = ea - sa; else ea = 360 + ea - sa; + t = r_get_style(ctx, node, "draw:fill"); + if (t) fill = 1; + ctx->drw->set_fg_color(drw_data, &fg); + ctx->drw->draw_arc(drw_data, + fill, + r_get_x(ctx, node, "svg:x"), r_get_y(ctx, node, "svg:y"), + r_get_x(ctx, node, "svg:width"), r_get_y(ctx, node, "svg:height"), + sa, ea + ); + } else if (strcmp(tag, "draw:polygon") == 0) { + // FIXME: + r_polygon(ctx, drw_data, node); + } else if (strcmp(tag, "draw:text-box") == 0) { + // FIXME: + r_text(ctx, drw_data, node); + } else if (strcmp(tag, "draw:image") == 0) { + char *name; + + name = iks_find_attrib(node, "xlink:href"); + if (!name) return; + if (name[0] == '#') ++name; + + _imp_draw_image(ctx, drw_data, + name, + r_get_x(ctx, node, "svg:x"), + r_get_y(ctx, node, "svg:y"), + r_get_x(ctx, node, "svg:width"), + r_get_y(ctx, node, "svg:height") + ); + } else { + printf("Unknown element: %s\n", tag); + } +} + +static void +render_page(ImpRenderCtx *ctx, void *drw_data) +{ + iks *x; + char *element; + int i; + + i = _imp_fill_back(ctx, drw_data, ctx->page->page); + element = iks_find_attrib(ctx->page->page, "draw:master-page-name"); + if (element) { + x = iks_find_with_attrib( + iks_find(ctx->page->doc->styles, "office:master-styles"), + "style:master-page", "style:name", element + ); + if (x) { + if (i == 0) _imp_fill_back(ctx, drw_data, x); + for (x = iks_first_tag(x); x; x = iks_next_tag(x)) { + if (iks_find_attrib(x, "presentation:class")) + continue; + render_object(ctx, drw_data, x); + } + } + } + for (x = iks_first_tag(ctx->page->page); x; x = iks_next_tag(x)) { + render_object(ctx, drw_data, x); + } +} + +static void +get_geometry(ImpRenderCtx *ctx) +{ + char *tmp; + iks *x, *y; + + tmp = iks_find_attrib(ctx->page->page, "draw:master-page-name"); + x = iks_find(ctx->page->doc->styles, "office:master-styles"); + y = iks_find_with_attrib(x, "style:master-page", "style:name", tmp); + x = iks_find(ctx->page->doc->styles, "office:automatic-styles"); + y = iks_find_with_attrib(x, "style:page-layout", "style:name", + iks_find_attrib(y, "style:page-layout-name")); + ctx->cm_w = atof(iks_find_attrib(iks_find(y, "style:page-layout-properties"), "fo:page-width")); + ctx->cm_h = atof(iks_find_attrib(iks_find(y, "style:page-layout-properties"), "fo:page-height")); +} + +int +_imp_oasis_load(ImpDoc *doc) +{ + ImpPage *page; + iks *x, *pres; + int i; + + pres = iks_find(iks_find(doc->content, "office:body"), "office:presentation"); + if (!pres) return IMP_NOTIMP; + + x = iks_find(pres, "draw:page"); + if (!x) return IMP_NOTIMP; + i = 0; + for (; x; x = iks_next_tag(x)) { + if (strcmp(iks_name(x), "draw:page") == 0) { + page = iks_stack_alloc(doc->stack, sizeof(ImpPage)); + if (!page) return IMP_NOMEM; + memset(page, 0, sizeof(ImpPage)); + page->page = x; + page->nr = ++i; + page->name = iks_find_attrib(x, "draw:name"); + page->doc = doc; + if (!doc->pages) doc->pages = page; + page->prev = doc->last_page; + if (doc->last_page) doc->last_page->next = page; + doc->last_page = page; + } + } + doc->nr_pages = i; + doc->get_geometry = get_geometry; + doc->render_page = render_page; + + return 0; +} diff --git a/backend/impress/f_oo13.c b/backend/impress/f_oo13.c new file mode 100644 index 00000000..7bfeeb53 --- /dev/null +++ b/backend/impress/f_oo13.c @@ -0,0 +1,181 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "internal.h" + +// { "draw:text-box", r_text }, +// { "draw:connector", r_line }, +// { "draw:polyline", r_polyline }, +// { "draw:polygon", r_polygon }, +// { "draw:path", r_path }, + +static void +render_object(ImpRenderCtx *ctx, void *drw_data, iks *node) +{ + char *tag, *t; + ImpColor fg; + + tag = iks_name(node); + if (strcmp(tag, "draw:g") == 0) { + iks *x; + for (x = iks_first_tag(node); x; x = iks_next_tag(x)) { + render_object(ctx, drw_data, x); + } + } else if (strcmp(tag, "draw:line") == 0) { + int x1, y1, x2, y2; + r_get_color(ctx, node, "svg:stroke-color", &fg); + ctx->drw->set_fg_color(drw_data, &fg); + x1 = r_get_x(ctx, node, "svg:x1"); + y1 = r_get_y(ctx, node, "svg:y1"); + x2 = r_get_x(ctx, node, "svg:x2"); + y2 = r_get_y(ctx, node, "svg:y2"); + ctx->drw->draw_line(drw_data, x1, y1, x2, y2); + if (r_get_style(ctx, node, "draw:marker-start")) { + _imp_draw_line_end(ctx, drw_data, 0, 0, x2, y2, x1, y1); + } + if (r_get_style(ctx, node, "draw:marker-end")) { + _imp_draw_line_end(ctx, drw_data, 0, 0, x1, y1, x2, y2); + } + } else if (strcmp(tag, "draw:rect") == 0) { + int x, y, w, h, r = 0; + char *t; + x = r_get_x(ctx, node, "svg:x"); + y = r_get_y(ctx, node, "svg:y"); + w = r_get_x(ctx, node, "svg:width"); + h = r_get_y(ctx, node, "svg:height"); + t = r_get_style(ctx, node, "draw:corner-radius"); + if (t) r = atof(t) * ctx->fact_x; + t = r_get_style(ctx, node, "draw:fill"); + if (t && strcmp(t, "none") != 0) { + r_get_color(ctx, node, "draw:fill-color", &fg); + ctx->drw->set_fg_color(drw_data, &fg); + _imp_draw_rect(ctx, drw_data, 1, x, y, w, h, r); + } + r_get_color(ctx, node, "svg:stroke-color", &fg); + ctx->drw->set_fg_color(drw_data, &fg); + _imp_draw_rect(ctx, drw_data, 0, x, y, w, h, r); + r_text(ctx, drw_data, node); + } else if (strcmp(tag, "draw:ellipse") == 0 || strcmp(tag, "draw:circle") == 0) { + int sa, ea, fill = 0; + r_get_color(ctx, node, "svg:stroke-color", &fg); + sa = r_get_angle(node, "draw:start-angle", 0); + ea = r_get_angle(node, "draw:end-angle", 360); + if (ea > sa) ea = ea - sa; else ea = 360 + ea - sa; + t = r_get_style(ctx, node, "draw:fill"); + if (t) fill = 1; + ctx->drw->set_fg_color(drw_data, &fg); + ctx->drw->draw_arc(drw_data, + fill, + r_get_x(ctx, node, "svg:x"), r_get_y(ctx, node, "svg:y"), + r_get_x(ctx, node, "svg:width"), r_get_y(ctx, node, "svg:height"), + sa, ea + ); + } else if (strcmp(tag, "draw:polygon") == 0) { + // FIXME: + r_polygon(ctx, drw_data, node); + } else if (strcmp(tag, "draw:text-box") == 0) { + // FIXME: + r_text(ctx, drw_data, node); + } else if (strcmp(tag, "draw:image") == 0) { + char *name; + + name = iks_find_attrib(node, "xlink:href"); + if (!name) return; + if (name[0] == '#') ++name; + + _imp_draw_image(ctx, drw_data, + name, + r_get_x(ctx, node, "svg:x"), + r_get_y(ctx, node, "svg:y"), + r_get_x(ctx, node, "svg:width"), + r_get_y(ctx, node, "svg:height") + ); + } else { + printf("Unknown element: %s\n", tag); + } +} + +static void +render_page(ImpRenderCtx *ctx, void *drw_data) +{ + iks *x; + char *element; + int i; + + i = _imp_fill_back(ctx, drw_data, ctx->page->page); + element = iks_find_attrib(ctx->page->page, "draw:master-page-name"); + if (element) { + x = iks_find_with_attrib( + iks_find(ctx->page->doc->styles, "office:master-styles"), + "style:master-page", "style:name", element + ); + if (x) { + if (i == 0) _imp_fill_back(ctx, drw_data, x); + for (x = iks_first_tag(x); x; x = iks_next_tag(x)) { + if (iks_find_attrib(x, "presentation:class")) + continue; + render_object(ctx, drw_data, x); + } + } + } + for (x = iks_first_tag(ctx->page->page); x; x = iks_next_tag(x)) { + render_object(ctx, drw_data, x); + } +} + +static void +get_geometry(ImpRenderCtx *ctx) +{ + char *tmp; + iks *x, *y; + + tmp = iks_find_attrib(ctx->page->page, "draw:master-page-name"); + x = iks_find(ctx->page->doc->styles, "office:master-styles"); + y = iks_find_with_attrib(x, "style:master-page", "style:name", tmp); + x = iks_find(ctx->page->doc->styles, "office:automatic-styles"); + y = iks_find_with_attrib(x, "style:page-master", "style:name", + iks_find_attrib(y, "style:page-master-name")); + ctx->cm_w = atof(iks_find_attrib(iks_find(y, "style:properties"), "fo:page-width")); + ctx->cm_h = atof(iks_find_attrib(iks_find(y, "style:properties"), "fo:page-height")); +} + +int +_imp_oo13_load(ImpDoc *doc) +{ + ImpPage *page; + char *class; + iks *x; + int i; + + class = iks_find_attrib(doc->content, "office:class"); + if (iks_strcmp(class, "presentation") != 0) return IMP_NOTIMP; + + x = iks_find(iks_find(doc->content, "office:body"), "draw:page"); + if (!x) return IMP_NOTIMP; + i = 0; + for (; x; x = iks_next_tag(x)) { + if (strcmp(iks_name(x), "draw:page") == 0) { + page = iks_stack_alloc(doc->stack, sizeof(ImpPage)); + if (!page) return IMP_NOMEM; + memset(page, 0, sizeof(ImpPage)); + page->page = x; + page->nr = ++i; + page->name = iks_find_attrib(x, "draw:name"); + page->doc = doc; + if (!doc->pages) doc->pages = page; + page->prev = doc->last_page; + if (doc->last_page) doc->last_page->next = page; + doc->last_page = page; + } + } + doc->nr_pages = i; + doc->get_geometry = get_geometry; + doc->render_page = render_page; + + return 0; +} diff --git a/backend/impress/iksemel.c b/backend/impress/iksemel.c new file mode 100644 index 00000000..9908e132 --- /dev/null +++ b/backend/impress/iksemel.c @@ -0,0 +1,1882 @@ +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2003 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +/* minimum sax buffer size */ +#define SAX_BUFFER_MIN_SIZE 128 + +/* sax parser structure plus extra data of dom parser */ +#define DEFAULT_DOM_CHUNK_SIZE 256 + +/* sax parser structure plus extra data of stream parser */ +#define DEFAULT_STREAM_CHUNK_SIZE 256 + +/* iks structure, its data, child iks structures, for stream parsing */ +#define DEFAULT_IKS_CHUNK_SIZE 1024 + +/* iks structure, its data, child iks structures, for file parsing */ +#define DEFAULT_DOM_IKS_CHUNK_SIZE 2048 + +/* rule structure and from/to/id/ns strings */ +#define DEFAULT_RULE_CHUNK_SIZE 128 + +/* file is read by blocks with this size */ +#define FILE_IO_BUF_SIZE 4096 + +/* network receive buffer */ +#define NET_IO_BUF_SIZE 4096 +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2003 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +#include +#include + +#include "common.h" +#include "iksemel.h" + +/***** malloc wrapper *****/ + +static void *(*my_malloc_func)(size_t size); +static void (*my_free_func)(void *ptr); + +void * +iks_malloc (size_t size) +{ + if (my_malloc_func) + return my_malloc_func (size); + else + return malloc (size); +} + +void +iks_free (void *ptr) +{ + if (my_free_func) + my_free_func (ptr); + else + free (ptr); +} + +void +iks_set_mem_funcs (void *(*malloc_func)(size_t size), void (*free_func)(void *ptr)) +{ + my_malloc_func = malloc_func; + my_free_func = free_func; +} + +/***** NULL-safe Functions *****/ + +char * +iks_strdup (const char *src) +{ + if (src) return strdup(src); + return NULL; +} + +char * +iks_strcat (char *dest, const char *src) +{ + size_t len; + + if (!src) return dest; + + len = strlen (src); + memcpy (dest, src, len); + dest[len] = '\0'; + return dest + len; +} + +int +iks_strcmp (const char *a, const char *b) +{ + if (!a || !b) return -1; + return strcmp (a, b); +} + +int +iks_strcasecmp (const char *a, const char *b) +{ + if (!a || !b) return -1; + return strcasecmp (a, b); +} + +int +iks_strncmp (const char *a, const char *b, size_t n) +{ + if (!a || !b) return -1; + return strncmp (a, b, n); +} + +int +iks_strncasecmp (const char *a, const char *b, size_t n) +{ + if (!a || !b) return -1; + return strncasecmp (a, b, n); +} + +size_t +iks_strlen (const char *src) +{ + if (!src) return 0; + return strlen (src); +} + +/***** XML Escaping *****/ + +char * +iks_escape (ikstack *s, char *src, size_t len) +{ + char *ret; + int i, j, nlen; + + if (!src || !s) return NULL; + if (len == -1) len = strlen (src); + + nlen = len; + for (i=0; i': nlen += 3; break; + case '\'': nlen += 5; break; + case '"': nlen += 5; break; + } + } + if (len == nlen) return src; + + ret = iks_stack_alloc (s, nlen + 1); + if (!ret) return NULL; + + for (i=j=0; i': memcpy (&ret[j], ">", 4); j += 4; break; + default: ret[j++] = src[i]; + } + } + ret[j] = '\0'; + + return ret; +} + +char * +iks_unescape (ikstack *s, char *src, size_t len) +{ + int i,j; + char *ret; + + if (!s || !src) return NULL; + if (!strchr (src, '&')) return src; + if (len == -1) len = strlen (src); + + ret = iks_stack_alloc (s, len + 1); + if (!ret) return NULL; + + for (i=j=0; i +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +#include "common.h" +#include "iksemel.h" + +struct align_test { char a; double b; }; +#define DEFAULT_ALIGNMENT ((size_t) ((char *) &((struct align_test *) 0)->b - (char *) 0)) +#define ALIGN_MASK ( DEFAULT_ALIGNMENT - 1 ) +#define MIN_CHUNK_SIZE ( DEFAULT_ALIGNMENT * 8 ) +#define MIN_ALLOC_SIZE DEFAULT_ALIGNMENT +#define ALIGN(x) ( (x) + (DEFAULT_ALIGNMENT - ( (x) & ALIGN_MASK)) ) + +typedef struct ikschunk_struct { + struct ikschunk_struct *next; + size_t size; + size_t used; + size_t last; + char data[4]; +} ikschunk; + +struct ikstack_struct { + size_t allocated; + ikschunk *meta; + ikschunk *data; +}; + +static ikschunk * +find_space (ikstack *s, ikschunk *c, size_t size) +{ + /* FIXME: dont use *2 after over allocated chunks */ + while (1) { + if (c->size - c->used >= size) return c; + if (!c->next) { + if ((c->size * 2) > size) size = c->size * 2; + c->next = iks_malloc (sizeof (ikschunk) + size); + if (!c->next) return NULL; + s->allocated += sizeof (ikschunk) + size; + c = c->next; + c->next = NULL; + c->size = size; + c->used = 0; + c->last = (size_t) -1; + return c; + } + c = c->next; + } + return NULL; +} + +ikstack * +iks_stack_new (size_t meta_chunk, size_t data_chunk) +{ + ikstack *s; + size_t len; + + if (meta_chunk < MIN_CHUNK_SIZE) meta_chunk = MIN_CHUNK_SIZE; + if (meta_chunk & ALIGN_MASK) meta_chunk = ALIGN (meta_chunk); + if (data_chunk < MIN_CHUNK_SIZE) data_chunk = MIN_CHUNK_SIZE; + if (data_chunk & ALIGN_MASK) data_chunk = ALIGN (data_chunk); + + len = sizeof (ikstack) + meta_chunk + data_chunk + (sizeof (ikschunk) * 2); + s = iks_malloc (len); + if (!s) return NULL; + s->allocated = len; + s->meta = (ikschunk *) ((char *) s + sizeof (ikstack)); + s->meta->next = NULL; + s->meta->size = meta_chunk; + s->meta->used = 0; + s->meta->last = (size_t) -1; + s->data = (ikschunk *) ((char *) s + sizeof (ikstack) + sizeof (ikschunk) + meta_chunk); + s->data->next = NULL; + s->data->size = data_chunk; + s->data->used = 0; + s->data->last = (size_t) -1; + return s; +} + +void * +iks_stack_alloc (ikstack *s, size_t size) +{ + ikschunk *c; + void *mem; + + if (size < MIN_ALLOC_SIZE) size = MIN_ALLOC_SIZE; + if (size & ALIGN_MASK) size = ALIGN (size); + + c = find_space (s, s->meta, size); + if (!c) return NULL; + mem = c->data + c->used; + c->used += size; + return mem; +} + +char * +iks_stack_strdup (ikstack *s, const char *src, size_t len) +{ + ikschunk *c; + char *dest; + + if (!src) return NULL; + if (0 == len) len = strlen (src); + + c = find_space (s, s->data, len + 1); + if (!c) return NULL; + dest = c->data + c->used; + c->last = c->used; + c->used += len + 1; + memcpy (dest, src, len); + dest[len] = '\0'; + return dest; +} + +char * +iks_stack_strcat (ikstack *s, char *old, size_t old_len, const char *src, size_t src_len) +{ + char *ret; + ikschunk *c; + + if (!old) { + return iks_stack_strdup (s, src, src_len); + } + if (0 == old_len) old_len = strlen (old); + if (0 == src_len) src_len = strlen (src); + + for (c = s->data; c; c = c->next) { + if (c->data + c->last == old) break; + } + if (!c) { + c = find_space (s, s->data, old_len + src_len + 1); + if (!c) return NULL; + ret = c->data + c->used; + c->last = c->used; + c->used += old_len + src_len + 1; + memcpy (ret, old, old_len); + memcpy (ret + old_len, src, src_len); + ret[old_len + src_len] = '\0'; + return ret; + } + + if (c->size - c->used > src_len) { + ret = c->data + c->last; + memcpy (ret + old_len, src, src_len); + c->used += src_len; + ret[old_len + src_len] = '\0'; + } else { + /* FIXME: decrease c->used before moving string to new place */ + c = find_space (s, s->data, old_len + src_len + 1); + if (!c) return NULL; + c->last = c->used; + ret = c->data + c->used; + memcpy (ret, old, old_len); + c->used += old_len; + memcpy (c->data + c->used, src, src_len); + c->used += src_len; + c->data[c->used] = '\0'; + c->used++; + } + return ret; +} + +void +iks_stack_stat (ikstack *s, size_t *allocated, size_t *used) +{ + ikschunk *c; + + if (allocated) { + *allocated = s->allocated; + } + if (used) { + *used = 0; + for (c = s->meta; c; c = c->next) { + (*used) += c->used; + } + for (c = s->data; c; c = c->next) { + (*used) += c->used; + } + } +} + +void +iks_stack_delete (ikstack *s) +{ + ikschunk *c, *tmp; + + c = s->meta->next; + while (c) { + tmp = c->next; + iks_free (c); + c = tmp; + } + c = s->data->next; + while (c) { + tmp = c->next; + iks_free (c); + c = tmp; + } + iks_free (s); +} +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2004 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +#include "common.h" +#include "iksemel.h" + +enum cons_e { + C_CDATA = 0, + C_TAG_START, + C_TAG, + C_TAG_END, + C_ATTRIBUTE, + C_ATTRIBUTE_1, + C_ATTRIBUTE_2, + C_VALUE, + C_VALUE_APOS, + C_VALUE_QUOT, + C_WHITESPACE, + C_ENTITY, + C_COMMENT, + C_COMMENT_1, + C_COMMENT_2, + C_COMMENT_3, + C_MARKUP, + C_MARKUP_1, + C_SECT, + C_SECT_CDATA, + C_SECT_CDATA_1, + C_SECT_CDATA_2, + C_SECT_CDATA_3, + C_SECT_CDATA_4, + C_SECT_CDATA_C, + C_SECT_CDATA_E, + C_SECT_CDATA_E2, + C_PI +}; + +/* if you add a variable here, dont forget changing iks_parser_reset */ +struct iksparser_struct { + ikstack *s; + void *user_data; + iksTagHook *tagHook; + iksCDataHook *cdataHook; + iksDeleteHook *deleteHook; + /* parser context */ + char *stack; + size_t stack_pos; + size_t stack_max; + + enum cons_e context; + enum cons_e oldcontext; + + char *tag_name; + enum ikstagtype tagtype; + + unsigned int attmax; + unsigned int attcur; + int attflag; + char **atts; + int valflag; + + unsigned int entpos; + char entity[8]; + + unsigned long nr_bytes; + unsigned long nr_lines; + + int uni_max; + int uni_len; +}; + +iksparser * +iks_sax_new (void *user_data, iksTagHook *tagHook, iksCDataHook *cdataHook) +{ + iksparser *prs; + + prs = iks_malloc (sizeof (iksparser)); + if (NULL == prs) return NULL; + memset (prs, 0, sizeof (iksparser)); + prs->user_data = user_data; + prs->tagHook = tagHook; + prs->cdataHook = cdataHook; + return prs; +} + +iksparser * +iks_sax_extend (ikstack *s, void *user_data, iksTagHook *tagHook, iksCDataHook *cdataHook, iksDeleteHook *deleteHook) +{ + iksparser *prs; + + prs = iks_stack_alloc (s, sizeof (iksparser)); + if (NULL == prs) return NULL; + memset (prs, 0, sizeof (iksparser)); + prs->s = s; + prs->user_data = user_data; + prs->tagHook = tagHook; + prs->cdataHook = cdataHook; + prs->deleteHook = deleteHook; + return prs; +} + +ikstack * +iks_parser_stack (iksparser *prs) +{ + return prs->s; +} + +void * +iks_user_data (iksparser *prs) +{ + return prs->user_data; +} + +unsigned long +iks_nr_bytes (iksparser *prs) +{ + return prs->nr_bytes; +} + +unsigned long +iks_nr_lines (iksparser *prs) +{ + return prs->nr_lines; +} + +#define IS_WHITESPACE(x) ' ' == (x) || '\t' == (x) || '\r' == (x) || '\n' == (x) +#define NOT_WHITESPACE(x) ' ' != (x) && '\t' != (x) && '\r' != (x) && '\n' != (x) + +static int +stack_init (iksparser *prs) +{ + prs->stack = iks_malloc (128); + if (!prs->stack) return 0; + prs->stack_max = 128; + prs->stack_pos = 0; + return 1; +} + +static int +stack_expand (iksparser *prs, int len) +{ + size_t need; + off_t diff; + char *tmp; + need = len - (prs->stack_max - prs->stack_pos); + if (need < prs->stack_max) { + need = prs->stack_max * 2; + } else { + need = prs->stack_max + (need * 1.2); + } + tmp = iks_malloc (need); + if (!tmp) return 0; + diff = tmp - prs->stack; + memcpy (tmp, prs->stack, prs->stack_max); + iks_free (prs->stack); + prs->stack = tmp; + prs->stack_max = need; + prs->tag_name += diff; + if (prs->attflag != 0) { + int i = 0; + while (i < (prs->attmax * 2)) { + if (prs->atts[i]) prs->atts[i] += diff; + i++; + } + } + return 1; +} + +#define STACK_INIT \ + if (NULL == prs->stack && 0 == stack_init (prs)) return IKS_NOMEM + +#define STACK_PUSH_START (prs->stack + prs->stack_pos) + +#define STACK_PUSH(buf,len) \ +{ \ + char *sbuf = (buf); \ + size_t slen = (len); \ + if (prs->stack_max - prs->stack_pos <= slen) { \ + if (0 == stack_expand (prs, slen)) return IKS_NOMEM; \ + } \ + memcpy (prs->stack + prs->stack_pos, sbuf, slen); \ + prs->stack_pos += slen; \ +} + +#define STACK_PUSH_END \ +{ \ + if (prs->stack_pos >= prs->stack_max) { \ + if (0 == stack_expand (prs, 1)) return IKS_NOMEM; \ + } \ + prs->stack[prs->stack_pos] = '\0'; \ + prs->stack_pos++; \ +} + +static enum ikserror +sax_core (iksparser *prs, char *buf, int len) +{ + enum ikserror err; + int pos = 0, old = 0, re, stack_old = -1; + unsigned char c; + + while (pos < len) { + re = 0; + c = buf[pos]; + if (0 == c || 0xFE == c || 0xFF == c) return IKS_BADXML; + if (prs->uni_max) { + if ((c & 0xC0) != 0x80) return IKS_BADXML; + prs->uni_len++; + if (prs->uni_len == prs->uni_max) prs->uni_max = 0; + goto cont; + } else { + if (c & 0x80) { + unsigned char mask; + if ((c & 0x60) == 0x40) { + prs->uni_max = 2; + mask = 0x1F; + } else if ((c & 0x70) == 0x60) { + prs->uni_max = 3; + mask = 0x0F; + } else if ((c & 0x78) == 0x70) { + prs->uni_max = 4; + mask = 0x07; + } else if ((c & 0x7C) == 0x78) { + prs->uni_max = 5; + mask = 0x03; + } else if ((c & 0x7E) == 0x7C) { + prs->uni_max = 6; + mask = 0x01; + } else { + return IKS_BADXML; + } + if ((c & mask) == 0) return IKS_BADXML; + prs->uni_len = 1; + if (stack_old == -1) stack_old = pos; + goto cont; + } + } + + switch (prs->context) { + case C_CDATA: + if ('&' == c) { + if (old < pos && prs->cdataHook) { + err = prs->cdataHook (prs->user_data, &buf[old], pos - old); + if (IKS_OK != err) return err; + } + prs->context = C_ENTITY; + prs->entpos = 0; + break; + } + if ('<' == c) { + if (old < pos && prs->cdataHook) { + err = prs->cdataHook (prs->user_data, &buf[old], pos - old); + if (IKS_OK != err) return err; + } + STACK_INIT; + prs->tag_name = STACK_PUSH_START; + if (!prs->tag_name) return IKS_NOMEM; + prs->context = C_TAG_START; + } + break; + + case C_TAG_START: + prs->context = C_TAG; + if ('/' == c) { + prs->tagtype = IKS_CLOSE; + break; + } + if ('?' == c) { + prs->context = C_PI; + break; + } + if ('!' == c) { + prs->context = C_MARKUP; + break; + } + prs->tagtype = IKS_OPEN; + stack_old = pos; + break; + + case C_TAG: + if (IS_WHITESPACE(c)) { + if (IKS_CLOSE == prs->tagtype) + prs->oldcontext = C_TAG_END; + else + prs->oldcontext = C_ATTRIBUTE; + prs->context = C_WHITESPACE; + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + break; + } + if ('/' == c) { + if (IKS_CLOSE == prs->tagtype) return IKS_BADXML; + prs->tagtype = IKS_SINGLE; + prs->context = C_TAG_END; + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + break; + } + if ('>' == c) { + prs->context = C_TAG_END; + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + re = 1; + } + if (stack_old == -1) stack_old = pos; + break; + + case C_TAG_END: + if (c != '>') return IKS_BADXML; + if (prs->tagHook) { + char **tmp; + if (prs->attcur == 0) tmp = NULL; else tmp = prs->atts; + err = prs->tagHook (prs->user_data, prs->tag_name, tmp, prs->tagtype); + if (IKS_OK != err) return err; + } + prs->stack_pos = 0; + stack_old = -1; + prs->attcur = 0; + prs->attflag = 0; + prs->context = C_CDATA; + old = pos + 1; + break; + + case C_ATTRIBUTE: + if ('/' == c) { + prs->tagtype = IKS_SINGLE; + prs->context = C_TAG_END; + break; + } + if ('>' == c) { + prs->context = C_TAG_END; + re = 1; + break; + } + if (!prs->atts) { + prs->attmax = 12; + prs->atts = iks_malloc (sizeof(char *) * 2 * 12); + if (!prs->atts) return IKS_NOMEM; + memset (prs->atts, 0, sizeof(char *) * 2 * 12); + prs->attcur = 0; + } else { + if (prs->attcur >= (prs->attmax * 2)) { + void *tmp; + prs->attmax += 12; + tmp = iks_malloc (sizeof(char *) * (2 * prs->attmax + 1)); + if (!tmp) return IKS_NOMEM; + memset (tmp, 0, sizeof(char *) * (2 * prs->attmax + 1)); + memcpy (tmp, prs->atts, sizeof(char *) * prs->attcur); + iks_free (prs->atts); + prs->atts = tmp; + } + } + prs->attflag = 1; + prs->atts[prs->attcur] = STACK_PUSH_START; + stack_old = pos; + prs->context = C_ATTRIBUTE_1; + break; + + case C_ATTRIBUTE_1: + if ('=' == c) { + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + prs->context = C_VALUE; + break; + } + if (stack_old == -1) stack_old = pos; + break; + + case C_ATTRIBUTE_2: + if ('/' == c) { + prs->tagtype = IKS_SINGLE; + prs->atts[prs->attcur] = NULL; + prs->context = C_TAG_END; + break; + } + if ('>' == c) { + prs->atts[prs->attcur] = NULL; + prs->context = C_TAG_END; + re = 1; + break; + } + prs->context = C_ATTRIBUTE; + re = 1; + break; + + case C_VALUE: + prs->atts[prs->attcur + 1] = STACK_PUSH_START; + if ('\'' == c) { + prs->context = C_VALUE_APOS; + break; + } + if ('"' == c) { + prs->context = C_VALUE_QUOT; + break; + } + return IKS_BADXML; + + case C_VALUE_APOS: + if ('\'' == c) { + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + prs->oldcontext = C_ATTRIBUTE_2; + prs->context = C_WHITESPACE; + prs->attcur += 2; + } + if (stack_old == -1) stack_old = pos; + break; + + case C_VALUE_QUOT: + if ('"' == c) { + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + prs->oldcontext = C_ATTRIBUTE_2; + prs->context = C_WHITESPACE; + prs->attcur += 2; + } + if (stack_old == -1) stack_old = pos; + break; + + case C_WHITESPACE: + if (NOT_WHITESPACE(c)) { + prs->context = prs->oldcontext; + re = 1; + } + break; + + case C_ENTITY: + if (';' == c) { + char hede[2]; + char t = '?'; + prs->entity[prs->entpos] = '\0'; + if (strcmp(prs->entity, "amp") == 0) + t = '&'; + else if (strcmp(prs->entity, "quot") == 0) + t = '"'; + else if (strcmp(prs->entity, "apos") == 0) + t = '\''; + else if (strcmp(prs->entity, "lt") == 0) + t = '<'; + else if (strcmp(prs->entity, "gt") == 0) + t = '>'; + old = pos + 1; + hede[0] = t; + if (prs->cdataHook) { + err = prs->cdataHook (prs->user_data, &hede[0], 1); + if (IKS_OK != err) return err; + } + prs->context = C_CDATA; + } else { + prs->entity[prs->entpos++] = buf[pos]; + if (prs->entpos > 7) return IKS_BADXML; + } + break; + + case C_COMMENT: + if ('-' != c) return IKS_BADXML; + prs->context = C_COMMENT_1; + break; + + case C_COMMENT_1: + if ('-' == c) prs->context = C_COMMENT_2; + break; + + case C_COMMENT_2: + if ('-' == c) + prs->context = C_COMMENT_3; + else + prs->context = C_COMMENT_1; + break; + + case C_COMMENT_3: + if ('>' != c) return IKS_BADXML; + prs->context = C_CDATA; + old = pos + 1; + break; + + case C_MARKUP: + if ('[' == c) { + prs->context = C_SECT; + break; + } + if ('-' == c) { + prs->context = C_COMMENT; + break; + } + prs->context = C_MARKUP_1; + + case C_MARKUP_1: + if ('>' == c) { + old = pos + 1; + prs->context = C_CDATA; + } + break; + + case C_SECT: + if ('C' == c) { + prs->context = C_SECT_CDATA; + break; + } + return IKS_BADXML; + + case C_SECT_CDATA: + if ('D' != c) return IKS_BADXML; + prs->context = C_SECT_CDATA_1; + break; + + case C_SECT_CDATA_1: + if ('A' != c) return IKS_BADXML; + prs->context = C_SECT_CDATA_2; + break; + + case C_SECT_CDATA_2: + if ('T' != c) return IKS_BADXML; + prs->context = C_SECT_CDATA_3; + break; + + case C_SECT_CDATA_3: + if ('A' != c) return IKS_BADXML; + prs->context = C_SECT_CDATA_4; + break; + + case C_SECT_CDATA_4: + if ('[' != c) return IKS_BADXML; + old = pos + 1; + prs->context = C_SECT_CDATA_C; + break; + + case C_SECT_CDATA_C: + if (']' == c) { + prs->context = C_SECT_CDATA_E; + if (prs->cdataHook && old < pos) { + err = prs->cdataHook (prs->user_data, &buf[old], pos - old); + if (IKS_OK != err) return err; + } + } + break; + + case C_SECT_CDATA_E: + if (']' == c) { + prs->context = C_SECT_CDATA_E2; + } else { + if (prs->cdataHook) { + err = prs->cdataHook (prs->user_data, "]", 1); + if (IKS_OK != err) return err; + } + old = pos; + prs->context = C_SECT_CDATA_C; + } + break; + + case C_SECT_CDATA_E2: + if ('>' == c) { + old = pos + 1; + prs->context = C_CDATA; + } else { + if (prs->cdataHook) { + err = prs->cdataHook (prs->user_data, "]]", 2); + if (IKS_OK != err) return err; + } + old = pos; + prs->context = C_SECT_CDATA_C; + } + break; + + case C_PI: + old = pos + 1; + if ('>' == c) prs->context = C_CDATA; + break; + } +cont: + if (0 == re) { + pos++; + prs->nr_bytes++; + if ('\n' == c) prs->nr_lines++; + } + } + + if (stack_old != -1) + STACK_PUSH (buf + stack_old, pos - stack_old); + + err = IKS_OK; + if (prs->cdataHook && (prs->context == C_CDATA || prs->context == C_SECT_CDATA_C) && old < pos) + err = prs->cdataHook (prs->user_data, &buf[old], pos - old); + return err; +} + +int +iks_parse (iksparser *prs, const char *data, size_t len, int finish) +{ + if (!data) return IKS_OK; + if (len == 0) len = strlen (data); + return sax_core (prs, (char *) data, len); +} + +void +iks_parser_reset (iksparser *prs) +{ + if (prs->deleteHook) prs->deleteHook (prs->user_data); + prs->stack_pos = 0; + prs->context = 0; + prs->oldcontext = 0; + prs->tagtype = 0; + prs->attcur = 0; + prs->attflag = 0; + prs->valflag = 0; + prs->entpos = 0; + prs->nr_bytes = 0; + prs->nr_lines = 0; + prs->uni_max = 0; + prs->uni_len = 0; +} + +void +iks_parser_delete (iksparser *prs) +{ + if (prs->deleteHook) prs->deleteHook (prs->user_data); + if (prs->stack) iks_free (prs->stack); + if (prs->atts) iks_free (prs->atts); + if (prs->s) iks_stack_delete (prs->s); else iks_free (prs); +} +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2004 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +#include "common.h" +#include "iksemel.h" + +#define IKS_COMMON \ + struct iks_struct *next, *prev; \ + struct iks_struct *parent; \ + enum ikstype type; \ + ikstack *s + +struct iks_struct { + IKS_COMMON; +}; + +struct iks_tag { + IKS_COMMON; + struct iks_struct *children, *last_child; + struct iks_struct *attribs, *last_attrib; + char *name; +}; + +#define IKS_TAG_NAME(x) ((struct iks_tag *) (x) )->name +#define IKS_TAG_CHILDREN(x) ((struct iks_tag *) (x) )->children +#define IKS_TAG_LAST_CHILD(x) ((struct iks_tag *) (x) )->last_child +#define IKS_TAG_ATTRIBS(x) ((struct iks_tag *) (x) )->attribs +#define IKS_TAG_LAST_ATTRIB(x) ((struct iks_tag *) (x) )->last_attrib + +struct iks_cdata { + IKS_COMMON; + char *cdata; + size_t len; +}; + +#define IKS_CDATA_CDATA(x) ((struct iks_cdata *) (x) )->cdata +#define IKS_CDATA_LEN(x) ((struct iks_cdata *) (x) )->len + +struct iks_attrib { + IKS_COMMON; + char *name; + char *value; +}; + +#define IKS_ATTRIB_NAME(x) ((struct iks_attrib *) (x) )->name +#define IKS_ATTRIB_VALUE(x) ((struct iks_attrib *) (x) )->value + +/***** Node Creating & Deleting *****/ + +iks * +iks_new (const char *name) +{ + ikstack *s; + iks *x; + + s = iks_stack_new (sizeof (struct iks_tag) * 6, 256); + if (!s) return NULL; + x = iks_new_within (name, s); + if (!x) { + iks_stack_delete (s); + return NULL; + } + return x; +} + +iks * +iks_new_within (const char *name, ikstack *s) +{ + iks *x; + size_t len; + + if (name) len = sizeof (struct iks_tag); else len = sizeof (struct iks_cdata); + x = iks_stack_alloc (s, len); + if (!x) return NULL; + memset (x, 0, len); + x->s = s; + x->type = IKS_TAG; + if (name) { + IKS_TAG_NAME (x) = iks_stack_strdup (s, name, 0); + if (!IKS_TAG_NAME (x)) return NULL; + } + return x; +} + +iks * +iks_insert (iks *x, const char *name) +{ + iks *y; + + if (!x) return NULL; + + y = iks_new_within (name, x->s); + if (!y) return NULL; + y->parent = x; + if (!IKS_TAG_CHILDREN (x)) IKS_TAG_CHILDREN (x) = y; + if (IKS_TAG_LAST_CHILD (x)) { + IKS_TAG_LAST_CHILD (x)->next = y; + y->prev = IKS_TAG_LAST_CHILD (x); + } + IKS_TAG_LAST_CHILD (x) = y; + return y; +} + +iks * +iks_insert_cdata (iks *x, const char *data, size_t len) +{ + iks *y; + + if(!x || !data) return NULL; + if(len == 0) len = strlen (data); + + y = IKS_TAG_LAST_CHILD (x); + if (y && y->type == IKS_CDATA) { + IKS_CDATA_CDATA (y) = iks_stack_strcat (x->s, IKS_CDATA_CDATA (y), IKS_CDATA_LEN (y), data, len); + IKS_CDATA_LEN (y) += len; + } else { + y = iks_insert (x, NULL); + if (!y) return NULL; + y->type = IKS_CDATA; + IKS_CDATA_CDATA (y) = iks_stack_strdup (x->s, data, len); + if (!IKS_CDATA_CDATA (y)) return NULL; + IKS_CDATA_LEN (y) = len; + } + return y; +} + +iks * +iks_insert_attrib (iks *x, const char *name, const char *value) +{ + iks *y; + size_t len; + + if (!x) return NULL; + + y = IKS_TAG_ATTRIBS (x); + while (y) { + if (strcmp (name, IKS_ATTRIB_NAME (y)) == 0) break; + y = y->next; + } + if (NULL == y) { + if (!value) return NULL; + y = iks_stack_alloc (x->s, sizeof (struct iks_attrib)); + if (!y) return NULL; + memset (y, 0, sizeof (struct iks_attrib)); + y->type = IKS_ATTRIBUTE; + IKS_ATTRIB_NAME (y) = iks_stack_strdup (x->s, name, 0); + y->parent = x; + if (!IKS_TAG_ATTRIBS (x)) IKS_TAG_ATTRIBS (x) = y; + if (IKS_TAG_LAST_ATTRIB (x)) { + IKS_TAG_LAST_ATTRIB (x)->next = y; + y->prev = IKS_TAG_LAST_ATTRIB (x); + } + IKS_TAG_LAST_ATTRIB (x) = y; + } + + if (value) { + len = strlen (value); + IKS_ATTRIB_VALUE (y) = iks_stack_strdup (x->s, value, len); + if (!IKS_ATTRIB_VALUE (y)) return NULL; + } else { + if (y->next) y->next->prev = y->prev; + if (y->prev) y->prev->next = y->next; + if (IKS_TAG_ATTRIBS (x) == y) IKS_TAG_ATTRIBS (x) = y->next; + if (IKS_TAG_LAST_ATTRIB (x) == y) IKS_TAG_LAST_ATTRIB (x) = y->prev; + } + + return y; +} + +iks * +iks_insert_node (iks *x, iks *y) +{ + y->parent = x; + if (!IKS_TAG_CHILDREN (x)) IKS_TAG_CHILDREN (x) = y; + if (IKS_TAG_LAST_CHILD (x)) { + IKS_TAG_LAST_CHILD (x)->next = y; + y->prev = IKS_TAG_LAST_CHILD (x); + } + IKS_TAG_LAST_CHILD (x) = y; + return y; +} + +void +iks_hide (iks *x) +{ + iks *y; + + if (!x) return; + + if (x->prev) x->prev->next = x->next; + if (x->next) x->next->prev = x->prev; + y = x->parent; + if (y) { + if (IKS_TAG_CHILDREN (y) == x) IKS_TAG_CHILDREN (y) = x->next; + if (IKS_TAG_LAST_CHILD (y) == x) IKS_TAG_LAST_CHILD (y) = x->prev; + } +} + +void +iks_delete (iks *x) +{ + if (x) iks_stack_delete (x->s); +} + +/***** Node Traversing *****/ + +iks * +iks_next (iks *x) +{ + if (x) return x->next; + return NULL; +} + +iks * +iks_next_tag (iks *x) +{ + if (x) { + while (1) { + x = x->next; + if (NULL == x) break; + if (IKS_TAG == x->type) return x; + } + } + return NULL; +} + +iks * +iks_prev (iks *x) +{ + if (x) return x->prev; + return NULL; +} + +iks * +iks_prev_tag (iks *x) +{ + if (x) { + while (1) { + x = x->prev; + if (NULL == x) break; + if (IKS_TAG == x->type) return x; + } + } + return NULL; +} + +iks * +iks_parent (iks *x) +{ + if (x) return x->parent; + return NULL; +} + +iks * +iks_root (iks *x) +{ + if (x) { + while (x->parent) + x = x->parent; + } + return x; +} + +iks * +iks_child (iks *x) +{ + if (x) return IKS_TAG_CHILDREN (x); + return NULL; +} + +iks * +iks_first_tag (iks *x) +{ + if (x) { + x = IKS_TAG_CHILDREN (x); + while (x) { + if (IKS_TAG == x->type) return x; + x = x->next; + } + } + return NULL; +} + +iks * +iks_attrib (iks *x) +{ + if (x) return IKS_TAG_ATTRIBS (x); + return NULL; +} + +iks * +iks_find (iks *x, const char *name) +{ + iks *y; + + if (!x) return NULL; + y = IKS_TAG_CHILDREN (x); + while (y) { + if (IKS_TAG == y->type && IKS_TAG_NAME (y) && strcmp (IKS_TAG_NAME (y), name) == 0) return y; + y = y->next; + } + return NULL; +} + +char * +iks_find_cdata (iks *x, const char *name) +{ + iks *y; + + y = iks_find (x, name); + if (!y) return NULL; + y = IKS_TAG_CHILDREN (y); + if (!y || IKS_CDATA != y->type) return NULL; + return IKS_CDATA_CDATA (y); +} + +char * +iks_find_attrib (iks *x, const char *name) +{ + iks *y; + + if (!x) return NULL; + + y = IKS_TAG_ATTRIBS (x); + while (y) { + if (IKS_ATTRIB_NAME (y) && strcmp (IKS_ATTRIB_NAME (y), name) == 0) + return IKS_ATTRIB_VALUE (y); + y = y->next; + } + return NULL; +} + +iks * +iks_find_with_attrib (iks *x, const char *tagname, const char *attrname, const char *value) +{ + iks *y; + + if (NULL == x) return NULL; + + if (tagname) { + for (y = IKS_TAG_CHILDREN (x); y; y = y->next) { + if (IKS_TAG == y->type + && strcmp (IKS_TAG_NAME (y), tagname) == 0 + && iks_strcmp (iks_find_attrib (y, attrname), value) == 0) { + return y; + } + } + } else { + for (y = IKS_TAG_CHILDREN (x); y; y = y->next) { + if (IKS_TAG == y->type + && iks_strcmp (iks_find_attrib (y, attrname), value) == 0) { + return y; + } + } + } + return NULL; +} + +/***** Node Information *****/ + +ikstack * +iks_stack (iks *x) +{ + if (x) return x->s; + return NULL; +} + +enum ikstype +iks_type (iks *x) +{ + if (x) return x->type; + return IKS_NONE; +} + +char * +iks_name (iks *x) +{ + if (x) { + if (IKS_TAG == x->type) + return IKS_TAG_NAME (x); + else + return IKS_ATTRIB_NAME (x); + } + return NULL; +} + +char * +iks_cdata (iks *x) +{ + if (x) { + if (IKS_CDATA == x->type) + return IKS_CDATA_CDATA (x); + else + return IKS_ATTRIB_VALUE (x); + } + return NULL; +} + +size_t +iks_cdata_size (iks *x) +{ + if (x) return IKS_CDATA_LEN (x); + return 0; +} + +int +iks_has_children (iks *x) +{ + if (x && IKS_TAG == x->type && IKS_TAG_CHILDREN (x)) return 1; + return 0; +} + +int +iks_has_attribs (iks *x) +{ + if (x && IKS_TAG == x->type && IKS_TAG_ATTRIBS (x)) return 1; + return 0; +} + +/***** Serializing *****/ + +static size_t +escape_size (char *src, size_t len) +{ + size_t sz; + char c; + int i; + + sz = 0; + for (i = 0; i < len; i++) { + c = src[i]; + switch (c) { + case '&': sz += 5; break; + case '\'': sz += 6; break; + case '"': sz += 6; break; + case '<': sz += 4; break; + case '>': sz += 4; break; + default: sz++; break; + } + } + return sz; +} + +static char * +my_strcat (char *dest, char *src, size_t len) +{ + if (0 == len) len = strlen (src); + memcpy (dest, src, len); + return dest + len; +} + +static char * +escape (char *dest, char *src, size_t len) +{ + char c; + int i; + int j = 0; + + for (i = 0; i < len; i++) { + c = src[i]; + if ('&' == c || '<' == c || '>' == c || '\'' == c || '"' == c) { + if (i - j > 0) dest = my_strcat (dest, src + j, i - j); + j = i + 1; + switch (c) { + case '&': dest = my_strcat (dest, "&", 5); break; + case '\'': dest = my_strcat (dest, "'", 6); break; + case '"': dest = my_strcat (dest, """, 6); break; + case '<': dest = my_strcat (dest, "<", 4); break; + case '>': dest = my_strcat (dest, ">", 4); break; + } + } + } + if (i - j > 0) dest = my_strcat (dest, src + j, i - j); + return dest; +} + +char * +iks_string (ikstack *s, iks *x) +{ + size_t size; + int level, dir; + iks *y, *z; + char *ret, *t; + + if (!x) return NULL; + + if (x->type == IKS_CDATA) { + if (s) { + return iks_stack_strdup (s, IKS_CDATA_CDATA (x), IKS_CDATA_LEN (x)); + } else { + ret = iks_malloc (IKS_CDATA_LEN (x)); + memcpy (ret, IKS_CDATA_CDATA (x), IKS_CDATA_LEN (x)); + return ret; + } + } + + size = 0; + level = 0; + dir = 0; + y = x; + while (1) { + if (dir==0) { + if (y->type == IKS_TAG) { + size++; + size += strlen (IKS_TAG_NAME (y)); + for (z = IKS_TAG_ATTRIBS (y); z; z = z->next) { + size += 4 + strlen (IKS_ATTRIB_NAME (z)) + + escape_size (IKS_ATTRIB_VALUE (z), strlen (IKS_ATTRIB_VALUE (z))); + } + if (IKS_TAG_CHILDREN (y)) { + size++; + y = IKS_TAG_CHILDREN (y); + level++; + continue; + } else { + size += 2; + } + } else { + size += escape_size (IKS_CDATA_CDATA (y), IKS_CDATA_LEN (y)); + } + } + z = y->next; + if (z) { + if (0 == level) { + if (IKS_TAG_CHILDREN (y)) size += 3 + strlen (IKS_TAG_NAME (y)); + break; + } + y = z; + dir = 0; + } else { + y = y->parent; + level--; + if (level >= 0) size += 3 + strlen (IKS_TAG_NAME (y)); + if (level < 1) break; + dir = 1; + } + } + + if (s) ret = iks_stack_alloc (s, size + 1); + else ret = iks_malloc (size + 1); + + if (!ret) return NULL; + + t = ret; + level = 0; + dir = 0; + while (1) { + if (dir==0) { + if (x->type == IKS_TAG) { + *t++ = '<'; + t = my_strcat (t, IKS_TAG_NAME (x), 0); + y = IKS_TAG_ATTRIBS (x); + while (y) { + *t++ = ' '; + t = my_strcat (t, IKS_ATTRIB_NAME (y), 0); + *t++ = '='; + *t++ = '\''; + t = escape (t, IKS_ATTRIB_VALUE (y), strlen (IKS_ATTRIB_VALUE (y))); + *t++ = '\''; + y = y->next; + } + if (IKS_TAG_CHILDREN (x)) { + *t++ = '>'; + x = IKS_TAG_CHILDREN (x); + level++; + continue; + } else { + *t++ = '/'; + *t++ = '>'; + } + } else { + t = escape (t, IKS_CDATA_CDATA (x), IKS_CDATA_LEN (x)); + } + } + y = x->next; + if (y) { + if (0 == level) { + if (IKS_TAG_CHILDREN (x)) { + *t++ = '<'; + *t++ = '/'; + t = my_strcat (t, IKS_TAG_NAME (x), 0); + *t++ = '>'; + } + break; + } + x = y; + dir = 0; + } else { + x = x->parent; + level--; + if (level >= 0) { + *t++ = '<'; + *t++ = '/'; + t = my_strcat (t, IKS_TAG_NAME (x), 0); + *t++ = '>'; + } + if (level < 1) break; + dir = 1; + } + } + *t = '\0'; + + return ret; +} + +/***** Copying *****/ + +iks * +iks_copy_within (iks *x, ikstack *s) +{ + int level=0, dir=0; + iks *copy = NULL; + iks *cur = NULL; + iks *y; + + while (1) { + if (dir == 0) { + if (x->type == IKS_TAG) { + if (copy == NULL) { + copy = iks_new_within (IKS_TAG_NAME (x), s); + cur = copy; + } else { + cur = iks_insert (cur, IKS_TAG_NAME (x)); + } + for (y = IKS_TAG_ATTRIBS (x); y; y = y->next) { + iks_insert_attrib (cur, IKS_ATTRIB_NAME (y), IKS_ATTRIB_VALUE (y)); + } + if (IKS_TAG_CHILDREN (x)) { + x = IKS_TAG_CHILDREN (x); + level++; + continue; + } else { + cur = cur->parent; + } + } else { + iks_insert_cdata (cur, IKS_CDATA_CDATA (x), IKS_CDATA_LEN (x)); + } + } + y = x->next; + if (y) { + if (0 == level) break; + x = y; + dir = 0; + } else { + if (level < 2) break; + level--; + x = x->parent; + cur = cur->parent; + dir = 1; + } + } + return copy; +} + +iks * +iks_copy (iks *x) +{ + return iks_copy_within (x, iks_stack_new (sizeof (struct iks_tag) * 6, 256)); +} +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2003 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +#include "common.h" +#include "iksemel.h" + +struct dom_data { + iks **iksptr; + iks *current; + size_t chunk_size; +}; + +static int +tagHook (struct dom_data *data, char *name, char **atts, int type) +{ + iks *x; + + if (IKS_OPEN == type || IKS_SINGLE == type) { + if (data->current) { + x = iks_insert (data->current, name); + } else { + ikstack *s; + s = iks_stack_new (data->chunk_size, data->chunk_size); + x = iks_new_within (name, s); + } + if (atts) { + int i=0; + while (atts[i]) { + iks_insert_attrib (x, atts[i], atts[i+1]); + i += 2; + } + } + data->current = x; + } + if (IKS_CLOSE == type || IKS_SINGLE == type) { + x = iks_parent (data->current); + if (x) + data->current = x; + else { + *(data->iksptr) = data->current; + data->current = NULL; + } + } + return IKS_OK; +} + +static int +cdataHook (struct dom_data *data, char *cdata, size_t len) +{ + if (data->current) iks_insert_cdata (data->current, cdata, len); + return IKS_OK; +} + +static void +deleteHook (struct dom_data *data) +{ + if (data->current) iks_delete (data->current); + data->current = NULL; +} + +iksparser * +iks_dom_new (iks **iksptr) +{ + ikstack *s; + struct dom_data *data; + + *iksptr = NULL; + s = iks_stack_new (DEFAULT_DOM_CHUNK_SIZE, 0); + if (!s) return NULL; + data = iks_stack_alloc (s, sizeof (struct dom_data)); + data->iksptr = iksptr; + data->current = NULL; + data->chunk_size = DEFAULT_DOM_IKS_CHUNK_SIZE; + return iks_sax_extend (s, data, (iksTagHook *) tagHook, (iksCDataHook *) cdataHook, (iksDeleteHook *) deleteHook); +} + +void +iks_set_size_hint (iksparser *prs, size_t approx_size) +{ + size_t cs; + struct dom_data *data = iks_user_data (prs); + + cs = approx_size / 10; + if (cs < DEFAULT_DOM_IKS_CHUNK_SIZE) cs = DEFAULT_DOM_IKS_CHUNK_SIZE; + data->chunk_size = cs; +} + +iks * +iks_tree (const char *xml_str, size_t len, int *err) +{ + iksparser *prs; + iks *x; + int e; + + if (0 == len) len = strlen (xml_str); + prs = iks_dom_new (&x); + if (!prs) { + if (err) *err = IKS_NOMEM; + return NULL; + } + e = iks_parse (prs, xml_str, len, 1); + if (err) *err = e; + iks_parser_delete (prs); + return x; +} + +int +iks_load (const char *fname, iks **xptr) +{ + iksparser *prs; + char *buf; + FILE *f; + int len, done = 0; + int ret; + + *xptr = NULL; + + buf = iks_malloc (FILE_IO_BUF_SIZE); + if (!buf) return IKS_NOMEM; + ret = IKS_NOMEM; + prs = iks_dom_new (xptr); + if (prs) { + f = fopen (fname, "r"); + if (f) { + while (0 == done) { + len = fread (buf, 1, FILE_IO_BUF_SIZE, f); + if (len < FILE_IO_BUF_SIZE) { + if (0 == feof (f)) { + ret = IKS_FILE_RWERR; + len = 0; + } + done = 1; + } + if (len > 0) { + int e; + e = iks_parse (prs, buf, len, done); + if (IKS_OK != e) { + ret = e; + break; + } + if (done) ret = IKS_OK; + } + } + fclose (f); + } else { + if (ENOENT == errno) ret = IKS_FILE_NOFILE; + else ret = IKS_FILE_NOACCESS; + } + iks_parser_delete (prs); + } + iks_free (buf); + return ret; +} + +int +iks_save (const char *fname, iks *x) +{ + FILE *f; + char *data; + int ret; + + ret = IKS_NOMEM; + data = iks_string (NULL, x); + if (data) { + ret = IKS_FILE_NOACCESS; + f = fopen (fname, "w"); + if (f) { + ret = IKS_FILE_RWERR; + if (fputs (data, f) >= 0) ret = IKS_OK; + fclose (f); + } + iks_free (data); + } + return ret; +} diff --git a/backend/impress/iksemel.h b/backend/impress/iksemel.h new file mode 100644 index 00000000..66c87d61 --- /dev/null +++ b/backend/impress/iksemel.h @@ -0,0 +1,402 @@ +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2004 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +#ifndef IKSEMEL_H +#define IKSEMEL_H 1 + +#ifdef __cplusplus +#include /* size_t for C++ */ +extern "C" { +#else +#include /* size_t for C */ +#endif + +/***** object stack *****/ + +struct ikstack_struct; +typedef struct ikstack_struct ikstack; + +ikstack *iks_stack_new (size_t meta_chunk, size_t data_chunk); +void *iks_stack_alloc (ikstack *s, size_t size); +char *iks_stack_strdup (ikstack *s, const char *src, size_t len); +char *iks_stack_strcat (ikstack *s, char *old, size_t old_len, const char *src, size_t src_len); +void iks_stack_stat (ikstack *s, size_t *allocated, size_t *used); +void iks_stack_delete (ikstack *s); + +/***** utilities *****/ + +void *iks_malloc (size_t size); +void iks_free (void *ptr); +void iks_set_mem_funcs (void *(*malloc_func)(size_t size), void (*free_func)(void *ptr)); + +char *iks_strdup (const char *src); +char *iks_strcat (char *dest, const char *src); +int iks_strcmp (const char *a, const char *b); +int iks_strcasecmp (const char *a, const char *b); +int iks_strncmp (const char *a, const char *b, size_t n); +int iks_strncasecmp (const char *a, const char *b, size_t n); +size_t iks_strlen (const char *src); +char *iks_escape (ikstack *s, char *src, size_t len); +char *iks_unescape (ikstack *s, char *src, size_t len); + +/***** dom tree *****/ + +enum ikstype { + IKS_NONE = 0, + IKS_TAG, + IKS_ATTRIBUTE, + IKS_CDATA +}; + +struct iks_struct; +typedef struct iks_struct iks; + +iks *iks_new (const char *name); +iks *iks_new_within (const char *name, ikstack *s); +iks *iks_insert (iks *x, const char *name); +iks *iks_insert_cdata (iks *x, const char *data, size_t len); +iks *iks_insert_attrib (iks *x, const char *name, const char *value); +iks *iks_insert_node (iks *x, iks *y); +void iks_hide (iks *x); +void iks_delete (iks *x); +iks *iks_next (iks *x); +iks *iks_next_tag (iks *x); +iks *iks_prev (iks *x); +iks *iks_prev_tag (iks *x); +iks *iks_parent (iks *x); +iks *iks_root (iks *x); +iks *iks_child (iks *x); +iks *iks_first_tag (iks *x); +iks *iks_attrib (iks *x); +iks *iks_find (iks *x, const char *name); +char *iks_find_cdata (iks *x, const char *name); +char *iks_find_attrib (iks *x, const char *name); +iks *iks_find_with_attrib (iks *x, const char *tagname, const char *attrname, const char *value); +ikstack *iks_stack (iks *x); +enum ikstype iks_type (iks *x); +char *iks_name (iks *x); +char *iks_cdata (iks *x); +size_t iks_cdata_size (iks *x); +int iks_has_children (iks *x); +int iks_has_attribs (iks *x); +char *iks_string (ikstack *s, iks *x); +iks *iks_copy (iks *x); +iks *iks_copy_within (iks *x, ikstack *s); + +/***** sax parser *****/ + +enum ikserror { + IKS_OK = 0, + IKS_NOMEM, + IKS_BADXML, + IKS_HOOK +}; + +enum ikstagtype { + IKS_OPEN, + IKS_CLOSE, + IKS_SINGLE +}; + +typedef int (iksTagHook)(void *user_data, char *name, char **atts, int type); +typedef int (iksCDataHook)(void *user_data, char *data, size_t len); +typedef void (iksDeleteHook)(void *user_data); + +struct iksparser_struct; +typedef struct iksparser_struct iksparser; + +iksparser *iks_sax_new (void *user_data, iksTagHook *tagHook, iksCDataHook *cdataHook); +iksparser *iks_sax_extend (ikstack *s, void *user_data, iksTagHook *tagHook, iksCDataHook *cdataHook, iksDeleteHook *deleteHook); +ikstack *iks_parser_stack (iksparser *prs); +void *iks_user_data (iksparser *prs); +unsigned long iks_nr_bytes (iksparser *prs); +unsigned long iks_nr_lines (iksparser *prs); +int iks_parse (iksparser *prs, const char *data, size_t len, int finish); +void iks_parser_reset (iksparser *prs); +void iks_parser_delete (iksparser *prs); + +/***** dom parser *****/ + +enum iksfileerror { + IKS_FILE_NOFILE = 4, + IKS_FILE_NOACCESS, + IKS_FILE_RWERR +}; + +iksparser *iks_dom_new (iks **iksptr); +void iks_set_size_hint (iksparser *prs, size_t approx_size); +iks *iks_tree (const char *xml_str, size_t len, int *err); +int iks_load (const char *fname, iks **xptr); +int iks_save (const char *fname, iks *x); + +/***** transport layer *****/ + +typedef void (iksTClose)(void *socket); +typedef int (iksTConnect)(iksparser *prs, void **socketptr, const char *server, int port); +typedef int (iksTSend)(void *socket, const char *data, size_t len); +typedef int (iksTRecv)(void *socket, char *buffer, size_t buf_len, int timeout); +typedef int (iksTConnectFD)(iksparser *prs, void **socketptr, void *fd); +typedef void *(iksTGetFD)(void *socket); + +enum iksasyncevents { + IKS_ASYNC_RESOLVED, + IKS_ASYNC_CONNECTED, + IKS_ASYNC_WRITE, + IKS_ASYNC_WRITTEN, + IKS_ASYNC_READ, + IKS_ASYNC_CLOSED, + IKS_ASYNC_ERROR +}; + +typedef int (iksAsyncNotify)(void *user_data, int event, void *event_data); +typedef int (iksTConnectAsync)(iksparser *prs, void **socketptr, const char *server, int port, void *notify_data, iksAsyncNotify *notify_func); + +typedef struct ikstransport_struct { + /* basic api, connect can be NULL if one of the other connect funcs are used */ + iksTConnect *connect; + iksTSend *send; + iksTRecv *recv; + iksTClose *close; + /* optional fd api */ + iksTConnectFD *connect_fd; + iksTGetFD *get_fd; + /* optional async api */ + iksTConnectAsync *connect_async; +} ikstransport; + +extern ikstransport iks_default_transport; + +/***** stream parser *****/ + +enum iksneterror { + IKS_NET_NODNS = 4, + IKS_NET_NOSOCK, + IKS_NET_NOCONN, + IKS_NET_RWERR, + IKS_NET_NOTSUPP, + IKS_NET_TLSFAIL +}; + +enum iksnodetype { + IKS_NODE_START, + IKS_NODE_NORMAL, + IKS_NODE_ERROR, + IKS_NODE_STOP +}; + +enum ikssasltype { + IKS_SASL_PLAIN, + IKS_SASL_DIGEST_MD5 +}; + +#define IKS_JABBER_PORT 5222 + +typedef int (iksStreamHook)(void *user_data, int type, iks *node); +typedef void (iksLogHook)(void *user_data, const char *data, size_t size, int is_incoming); + +iksparser *iks_stream_new (char *name_space, void *user_data, iksStreamHook *streamHook); +void *iks_stream_user_data (iksparser *prs); +void iks_set_log_hook (iksparser *prs, iksLogHook *logHook); +int iks_connect_tcp (iksparser *prs, const char *server, int port); +int iks_connect_fd (iksparser *prs, int fd); +int iks_connect_via (iksparser *prs, const char *server, int port, const char *server_name); +int iks_connect_with (iksparser *prs, const char *server, int port, const char *server_name, ikstransport *trans); +int iks_connect_async (iksparser *prs, const char *server, int port, void *notify_data, iksAsyncNotify *notify_func); +int iks_connect_async_with (iksparser *prs, const char *server, int port, const char *server_name, ikstransport *trans, void *notify_data, iksAsyncNotify *notify_func); +int iks_fd (iksparser *prs); +int iks_recv (iksparser *prs, int timeout); +int iks_send_header (iksparser *prs, const char *to); +int iks_send (iksparser *prs, iks *x); +int iks_send_raw (iksparser *prs, const char *xmlstr); +void iks_disconnect (iksparser *prs); +int iks_has_tls (void); +int iks_is_secure (iksparser *prs); +int iks_start_tls (iksparser *prs); +int iks_start_sasl (iksparser *prs, enum ikssasltype type, char *username, char *pass); + +/***** jabber *****/ + +#define IKS_NS_CLIENT "jabber:client" +#define IKS_NS_SERVER "jabber:server" +#define IKS_NS_AUTH "jabber:iq:auth" +#define IKS_NS_AUTH_0K "jabber:iq:auth:0k" +#define IKS_NS_REGISTER "jabber:iq:register" +#define IKS_NS_ROSTER "jabber:iq:roster" +#define IKS_NS_XROSTER "jabber:x:roster" +#define IKS_NS_OFFLINE "jabber:x:offline" +#define IKS_NS_AGENT "jabber:iq:agent" +#define IKS_NS_AGENTS "jabber:iq:agents" +#define IKS_NS_BROWSE "jabber:iq:browse" +#define IKS_NS_CONFERENCE "jabber:iq:conference" +#define IKS_NS_DELAY "jabber:x:delay" +#define IKS_NS_VERSION "jabber:iq:version" +#define IKS_NS_TIME "jabber:iq:time" +#define IKS_NS_VCARD "vcard-temp" +#define IKS_NS_PRIVATE "jabber:iq:private" +#define IKS_NS_SEARCH "jabber:iq:search" +#define IKS_NS_OOB "jabber:iq:oob" +#define IKS_NS_XOOB "jabber:x:oob" +#define IKS_NS_ADMIN "jabber:iq:admin" +#define IKS_NS_FILTER "jabber:iq:filter" +#define IKS_NS_GATEWAY "jabber:iq:gateway" +#define IKS_NS_LAST "jabber:iq:last" +#define IKS_NS_SIGNED "jabber:x:signed" +#define IKS_NS_ENCRYPTED "jabber:x:encrypted" +#define IKS_NS_ENVELOPE "jabber:x:envelope" +#define IKS_NS_EVENT "jabber:x:event" +#define IKS_NS_EXPIRE "jabber:x:expire" +#define IKS_NS_XHTML "http://www.w3.org/1999/xhtml" +#define IKS_NS_XMPP_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +#define IKS_NS_XMPP_BIND "urn:ietf:params:xml:ns:xmpp-bind" +#define IKS_NS_XMPP_SESSION "urn:ietf:params:xml:ns:xmpp-session" + +#define IKS_ID_USER 1 +#define IKS_ID_SERVER 2 +#define IKS_ID_RESOURCE 4 +#define IKS_ID_PARTIAL IKS_ID_USER | IKS_ID_SERVER +#define IKS_ID_FULL IKS_ID_USER | IKS_ID_SERVER | IKS_ID_RESOURCE + +#define IKS_STREAM_STARTTLS 1 +#define IKS_STREAM_SESSION 2 +#define IKS_STREAM_BIND 4 +#define IKS_STREAM_SASL_PLAIN 8 +#define IKS_STREAM_SASL_MD5 16 + +typedef struct iksid_struct { + char *user; + char *server; + char *resource; + char *partial; + char *full; +} iksid; + +iksid *iks_id_new (ikstack *s, const char *jid); +int iks_id_cmp (iksid *a, iksid *b, int parts); + +enum ikspaktype { + IKS_PAK_NONE = 0, + IKS_PAK_MESSAGE, + IKS_PAK_PRESENCE, + IKS_PAK_IQ, + IKS_PAK_S10N +}; + +enum iksubtype { + IKS_TYPE_NONE = 0, + IKS_TYPE_ERROR, + + IKS_TYPE_CHAT, + IKS_TYPE_GROUPCHAT, + IKS_TYPE_HEADLINE, + + IKS_TYPE_GET, + IKS_TYPE_SET, + IKS_TYPE_RESULT, + + IKS_TYPE_SUBSCRIBE, + IKS_TYPE_SUBSCRIBED, + IKS_TYPE_UNSUBSCRIBE, + IKS_TYPE_UNSUBSCRIBED, + IKS_TYPE_PROBE, + IKS_TYPE_AVAILABLE, + IKS_TYPE_UNAVAILABLE +}; + +enum ikshowtype { + IKS_SHOW_UNAVAILABLE = 0, + IKS_SHOW_AVAILABLE, + IKS_SHOW_CHAT, + IKS_SHOW_AWAY, + IKS_SHOW_XA, + IKS_SHOW_DND +}; + +typedef struct ikspak_struct { + iks *x; + iksid *from; + iks *query; + char *ns; + char *id; + enum ikspaktype type; + enum iksubtype subtype; + enum ikshowtype show; +} ikspak; + +ikspak *iks_packet (iks *x); + +iks *iks_make_auth (iksid *id, const char *pass, const char *sid); +iks *iks_make_msg (enum iksubtype type, const char *to, const char *body); +iks *iks_make_s10n (enum iksubtype type, const char *to, const char *msg); +iks *iks_make_pres (enum ikshowtype show, const char *status); +iks *iks_make_iq (enum iksubtype type, const char *xmlns); +iks *iks_make_resource_bind(iksid *id); +iks *iks_make_session(void); +int iks_stream_features(iks *x); + +/***** jabber packet filter *****/ + +#define IKS_RULE_DONE 0 +#define IKS_RULE_ID 1 +#define IKS_RULE_TYPE 2 +#define IKS_RULE_SUBTYPE 4 +#define IKS_RULE_FROM 8 +#define IKS_RULE_FROM_PARTIAL 16 +#define IKS_RULE_NS 32 + +enum iksfilterret { + IKS_FILTER_PASS, + IKS_FILTER_EAT +}; + +typedef int (iksFilterHook)(void *user_data, ikspak *pak); + +struct iksfilter_struct; +typedef struct iksfilter_struct iksfilter; +struct iksrule_struct; +typedef struct iksrule_struct iksrule; + +iksfilter *iks_filter_new (void); +iksrule *iks_filter_add_rule (iksfilter *f, iksFilterHook *filterHook, void *user_data, ...); +void iks_filter_remove_rule (iksfilter *f, iksrule *rule); +void iks_filter_remove_hook (iksfilter *f, iksFilterHook *filterHook); +void iks_filter_packet (iksfilter *f, ikspak *pak); +void iks_filter_delete (iksfilter *f); + +/***** sha1 *****/ + +struct iksha_struct; +typedef struct iksha_struct iksha; + +iksha *iks_sha_new (void); +void iks_sha_reset (iksha *sha); +void iks_sha_hash (iksha *sha, const unsigned char *data, size_t len, int finish); +void iks_sha_print (iksha *sha, char *hash); +void iks_sha_delete (iksha *sha); +void iks_sha (const char *data, char *hash); + +/***** md5 *****/ + +struct ikmd5_struct; +typedef struct iksmd5_struct iksmd5; + +iksmd5 *iks_md5_new(void); +void iks_md5_reset(iksmd5 *md5); +void iks_md5_hash(iksmd5 *md5, const unsigned char *data, size_t slen, int finish); +void iks_md5_delete(iksmd5 *md5); +void iks_md5_print(iksmd5 *md5, char *buf); +void iks_md5_digest(iksmd5 *md5, unsigned char *digest); +void iks_md5(const char *data, char *buf); + +/***** base64 *****/ + +char *iks_base64_decode(const char *buf); +char *iks_base64_encode(const char *buf, int len); + +#ifdef __cplusplus +} +#endif + +#endif /* IKSEMEL_H */ diff --git a/backend/impress/imposter.h b/backend/impress/imposter.h new file mode 100644 index 00000000..50c87f2c --- /dev/null +++ b/backend/impress/imposter.h @@ -0,0 +1,84 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#ifndef IMPOSTER_H +#define IMPOSTER_H + +#include + +enum { + IMP_OK = 0, + IMP_NOMEM, + IMP_NOTZIP, + IMP_BADZIP, + IMP_BADDOC, + IMP_NOTIMP +}; + +struct ImpDoc_struct; +typedef struct ImpDoc_struct ImpDoc; + +struct ImpPage_struct; +typedef struct ImpPage_struct ImpPage; + +typedef struct ImpPointStruct { + int x; + int y; +} ImpPoint; + +typedef struct ImpColorStruct { + int red; + int green; + int blue; +} ImpColor; + +#define IMP_NORMAL 0 +#define IMP_BOLD 1 +#define IMP_ITALIC 2 +#define IMP_UNDERLINE 4 + +typedef struct ImpDrawer_struct { + void (*get_size)(void *drw_data, int *w, int *h); + void (*set_fg_color)(void *drw_data, ImpColor *color); + void (*draw_line)(void *drw_data, int x1, int y1, int x2, int y2); + void (*draw_rect)(void *drw_data, int fill, int x, int y, int w, int h); + void (*draw_polygon)(void *drw_data, int fill, ImpPoint *pts, int nr_pts); + void (*draw_arc)(void *drw_data, int fill, int x, int y, int w, int h, int sa, int ea); + void (*draw_bezier)(void *drw_data, int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3); + void *(*open_image)(void *drw_data, const unsigned char *pix, size_t size); + void (*get_image_size)(void *drw_data, void *img_data, int *w, int *h); + void *(*scale_image)(void *drw_data, void *img_data, int w, int h); + void (*draw_image)(void *drw_data, void *img_data, int x, int y, int w, int h); + void (*close_image)(void *drw_data, void *img_data); + void (*get_text_size)(void *drw_data, const char *text, size_t len, int size, int styles, int *w, int *h); + void (*draw_text)(void *drw_data, int x, int y, const char *text, size_t len, int size, int styles); +} ImpDrawer; + +struct ImpRenderCtx_struct; +typedef struct ImpRenderCtx_struct ImpRenderCtx; + +#define IMP_LAST_PAGE -1 + +ImpDoc *imp_open(const char *filename, int *err); +int imp_nr_pages(ImpDoc *doc); +ImpPage *imp_get_page(ImpDoc *doc, int page_no); +void imp_close(ImpDoc *doc); + +void *imp_get_xml(ImpDoc *doc, const char *filename); + +ImpPage *imp_next_page(ImpPage *page); +ImpPage *imp_prev_page(ImpPage *page); +int imp_get_page_no(ImpPage *page); +const char *imp_get_page_name(ImpPage *page); + +ImpRenderCtx *imp_create_context(const ImpDrawer *drw); +void imp_context_set_page(ImpRenderCtx *ctx, ImpPage *page); +void imp_context_set_step(ImpRenderCtx *ctx, int step); +void imp_render(ImpRenderCtx *ctx, void *drw_data); +void imp_delete_context(ImpRenderCtx *ctx); + + +#endif /* IMPOSTER_H */ diff --git a/backend/impress/impress-document.c b/backend/impress/impress-document.c new file mode 100644 index 00000000..5254a881 --- /dev/null +++ b/backend/impress/impress-document.c @@ -0,0 +1,548 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* + * Copyright (C) 2005, Jonathan Blandford + * Copyright (C) 2005, Bastien Nocera + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include + +#include +#include + +#include "imposter.h" +#include "impress-document.h" + +#include "ev-document-misc.h" +#include "ev-document-thumbnails.h" + +struct _ImpressDocumentClass +{ + EvDocumentClass parent_class; +}; + +struct _ImpressDocument +{ + EvDocument parent_instance; + + ImpDoc *imp; + ImpRenderCtx *ctx; + + GMutex *mutex; + GdkPixmap *pixmap; + GdkGC *gc; + PangoContext *pango_ctx; + + /* Only used while rendering inside the mainloop */ + int pagenum; + GdkPixbuf *pixbuf; + GCond *cond; +}; + +#define PAGE_WIDTH 1024 +#define PAGE_HEIGHT 768 + +typedef struct _ImpressDocumentClass ImpressDocumentClass; + +static void impress_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); + +EV_BACKEND_REGISTER_WITH_CODE (ImpressDocument, impress_document, + { + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, + impress_document_document_thumbnails_iface_init); + }); + +/* Renderer */ +static void +imp_render_draw_bezier_real (GdkDrawable *d, GdkGC *gc, int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3) +{ + int x, y, nx, ny; + int ax, bx, cx, ay, by, cy; + double t, t2, t3; + + x = x0; + y = y0; + + cx = 3 * (x1 - x0); + bx = 3 * (x2 - x1) - cx; + ax = x3 - x0 - cx - bx; + cy = 3 * (y1 - y0); + by = 3 * (y2 - y1) - cy; + ay = y3 - y0 - cy - by; + + for (t = 0; t < 1; t += 0.01) { + t2 = t * t; + t3 = t2 * t; + nx = ax * t3 + bx * t2 + cx * t + x0; + ny = ay * t3 + by * t2 + cy * t + y0; + gdk_draw_line (d, gc, x, y, nx, ny); + x = nx; + y = ny; + } +} + +static void +imp_render_get_size(void *drw_data, int *w, int *h) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (drw_data); + + #if GTK_CHECK_VERSION(3, 0, 0) + *w = gdk_window_get_width(impress_document->pixmap); + *h = gdk_window_get_height(impress_document->pixmap); + #else + gdk_drawable_get_size(impress_document->pixmap, w, h); + #endif +} + +static void +imp_render_set_fg_color(void *drw_data, ImpColor *color) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (drw_data); + GdkColor c; + + c.red = color->red; + c.green = color->green; + c.blue = color->blue; + gdk_gc_set_rgb_fg_color(impress_document->gc, &c); +} + +static void +imp_render_draw_line(void *drw_data, int x1, int y1, int x2, int y2) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (drw_data); + + gdk_draw_line(impress_document->pixmap, impress_document->gc, x1, y1, x2, y2); +} + +static void +imp_render_draw_rect(void *drw_data, int fill, int x, int y, int w, int h) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (drw_data); + + gdk_draw_rectangle(impress_document->pixmap, impress_document->gc, fill, x, y, w, h); +} + +static void +imp_render_draw_polygon(void *drw_data, int fill, ImpPoint *pts, int nr_pts) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (drw_data); + + gdk_draw_polygon(impress_document->pixmap, impress_document->gc, fill, (GdkPoint *)pts, nr_pts); +} + +static void +imp_render_draw_arc(void *drw_data, int fill, int x, int y, int w, int h, int sa, int ea) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (drw_data); + + gdk_draw_arc(impress_document->pixmap, impress_document->gc, fill, x, y, w, h, sa * 64, ea * 64); +} + +static void +imp_render_draw_bezier(void *drw_data, int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (drw_data); + + imp_render_draw_bezier_real (impress_document->pixmap, impress_document->gc, x0, y0, x1, y1, x2, y2, x3, y3); +} + +static void * +imp_render_open_image(void *drw_data, const unsigned char *pix, size_t size) +{ + GdkPixbufLoader *gpl; + GdkPixbuf *pb; + + gpl = gdk_pixbuf_loader_new(); + gdk_pixbuf_loader_write(gpl, pix, size, NULL); + gdk_pixbuf_loader_close(gpl, NULL); + pb = gdk_pixbuf_loader_get_pixbuf(gpl); + return pb; +} + +static void +imp_render_get_image_size(void *drw_data, void *img_data, int *w, int *h) +{ + GdkPixbuf *pb = (GdkPixbuf *) img_data; + + *w = gdk_pixbuf_get_width(pb); + *h = gdk_pixbuf_get_height(pb); +} + +static void * +imp_render_scale_image(void *drw_data, void *img_data, int w, int h) +{ + GdkPixbuf *pb = (GdkPixbuf *) img_data; + + return gdk_pixbuf_scale_simple(pb, w, h, GDK_INTERP_BILINEAR); +} + +static void +imp_render_draw_image(void *drw_data, void *img_data, int x, int y, int w, int h) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (drw_data); + GdkPixbuf *pb = (GdkPixbuf *) img_data; + + gdk_draw_pixbuf(impress_document->pixmap, impress_document->gc, pb, 0, 0, x, y, w, h, GDK_RGB_DITHER_NONE, 0, 0); +} + +static void +imp_render_close_image(void *drw_data, void *img_data) +{ + GdkPixbuf *pb = (GdkPixbuf *) img_data; + + g_object_unref(G_OBJECT(pb)); +} + +static char * +imp_render_markup(const char *text, size_t len, int styles, int size) +{ + double scr_mm, scr_px, dpi; + char *esc; + char *ret; + int sz; + + scr_mm = gdk_screen_get_height_mm(gdk_screen_get_default()); + scr_px = gdk_screen_get_height(gdk_screen_get_default()); + dpi = (scr_px / scr_mm) * 25.4; + sz = (int) ((double) size * 72.0 * PANGO_SCALE / dpi); + esc = g_markup_escape_text(text, len); + ret = g_strdup_printf("%s", sz, esc); + g_free(esc); + return ret; +} + +static void +imp_render_get_text_size(void *drw_data, const char *text, size_t len, int size, int styles, int *w, int *h) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (drw_data); + PangoLayout *lay; + int pw, ph; + char *m; + + g_return_if_fail (impress_document->pango_ctx != NULL); + + lay = pango_layout_new(impress_document->pango_ctx); + m = imp_render_markup(text, len, styles, size); + pango_layout_set_markup(lay, m, strlen(m)); + pango_layout_get_size(lay, &pw, &ph); + g_object_unref(lay); + g_free(m); + *w = pw / PANGO_SCALE; + *h = ph / PANGO_SCALE; +} + +static void +imp_render_draw_text(void *drw_data, int x, int y, const char *text, size_t len, int size, int styles) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (drw_data); + PangoLayout *lay; + char *m; + + g_return_if_fail (impress_document->pango_ctx != NULL); + + lay = pango_layout_new(impress_document->pango_ctx); + m = imp_render_markup(text, len, styles, size); + pango_layout_set_markup(lay, m, strlen(m)); + gdk_draw_layout(impress_document->pixmap, impress_document->gc, x, y, lay); + g_object_unref(lay); + g_free(m); +} + +static const ImpDrawer imp_render_functions = { + imp_render_get_size, + imp_render_set_fg_color, + imp_render_draw_line, + imp_render_draw_rect, + imp_render_draw_polygon, + imp_render_draw_arc, + imp_render_draw_bezier, + imp_render_open_image, + imp_render_get_image_size, + imp_render_scale_image, + imp_render_draw_image, + imp_render_close_image, + imp_render_get_text_size, + imp_render_draw_text +}; + +/* Document interface */ +static gboolean +impress_document_load (EvDocument *document, + const char *uri, + GError **error) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (document); + gchar *filename; + ImpDoc *imp; + int err; + + /* FIXME: Could we actually load uris ? */ + filename = g_filename_from_uri (uri, NULL, error); + if (!filename) + return FALSE; + + imp = imp_open (filename, &err); + g_free (filename); + + if (!imp) + { + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("Invalid document")); + return FALSE; + } + impress_document->imp = imp; + + return TRUE; +} + +static gboolean +impress_document_save (EvDocument *document, + const char *uri, + GError **error) +{ + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + "Not supported"); + return FALSE; +} + +static int +impress_document_get_n_pages (EvDocument *document) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (document); + + g_return_val_if_fail (IMPRESS_IS_DOCUMENT (document), 0); + g_return_val_if_fail (impress_document->imp != NULL, 0); + + return imp_nr_pages (impress_document->imp); +} + +static void +impress_document_get_page_size (EvDocument *document, + EvPage *page, + double *width, + double *height) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (document); + + g_return_if_fail (IMPRESS_IS_DOCUMENT (document)); + g_return_if_fail (impress_document->imp != NULL); + + //FIXME + *width = PAGE_WIDTH; + *height = PAGE_HEIGHT; +} + +static gboolean +imp_render_get_from_drawable (ImpressDocument *impress_document) +{ + ImpPage *page; + + page = imp_get_page (impress_document->imp, impress_document->pagenum); + + g_return_val_if_fail (page != NULL, FALSE); + + ev_document_doc_mutex_lock (); + imp_context_set_page (impress_document->ctx, page); + imp_render (impress_document->ctx, impress_document); + ev_document_doc_mutex_unlock (); + + impress_document->pixbuf = gdk_pixbuf_get_from_drawable (NULL, + GDK_DRAWABLE (impress_document->pixmap), + NULL, + 0, 0, + 0, 0, + PAGE_WIDTH, PAGE_HEIGHT); + + g_cond_broadcast (impress_document->cond); + return FALSE; +} + +static GdkPixbuf * +impress_document_render_pixbuf (EvDocument *document, + EvRenderContext *rc) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (document); + GdkPixbuf *pixbuf; + + g_return_val_if_fail (IMPRESS_IS_DOCUMENT (document), NULL); + g_return_val_if_fail (impress_document->imp != NULL, NULL); + + impress_document->pagenum = rc->page->index; + + g_mutex_lock (impress_document->mutex); + impress_document->cond = g_cond_new (); + + ev_document_fc_mutex_unlock (); + ev_document_doc_mutex_unlock (); + g_idle_add ((GSourceFunc) imp_render_get_from_drawable, impress_document); + g_cond_wait (impress_document->cond, impress_document->mutex); + g_cond_free (impress_document->cond); + ev_document_doc_mutex_lock (); + ev_document_fc_mutex_lock (); + + g_mutex_unlock (impress_document->mutex); + + pixbuf = impress_document->pixbuf; + impress_document->pixbuf = NULL; + + return pixbuf; +} + +static cairo_surface_t * +impress_document_render (EvDocument *document, + EvRenderContext *rc) +{ + GdkPixbuf *pixbuf; + cairo_surface_t *surface, *scaled_surface; + + pixbuf = impress_document_render_pixbuf (document, rc); + + /* FIXME: impress backend should be ported to cairo */ + surface = ev_document_misc_surface_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + + scaled_surface = ev_document_misc_surface_rotate_and_scale (surface, + (PAGE_WIDTH * rc->scale) + 0.5, + (PAGE_HEIGHT * rc->scale) + 0.5, + rc->rotation); + cairo_surface_destroy (surface); + + return scaled_surface; +} + +static void +impress_document_finalize (GObject *object) +{ + ImpressDocument *impress_document = IMPRESS_DOCUMENT (object); + + if (impress_document->mutex) + g_mutex_free (impress_document->mutex); + + if (impress_document->imp) + imp_close (impress_document->imp); + + if (impress_document->ctx) + imp_delete_context (impress_document->ctx); + + if (impress_document->pango_ctx) + g_object_unref (impress_document->pango_ctx); + + if (impress_document->pixmap) + g_object_unref (G_OBJECT (impress_document->pixmap)); + + if (impress_document->gc) + g_object_unref (impress_document->gc); + + G_OBJECT_CLASS (impress_document_parent_class)->finalize (object); +} + +static void +impress_document_class_init (ImpressDocumentClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass); + + gobject_class->finalize = impress_document_finalize; + + ev_document_class->load = impress_document_load; + ev_document_class->save = impress_document_save; + ev_document_class->get_n_pages = impress_document_get_n_pages; + ev_document_class->get_page_size = impress_document_get_page_size; + ev_document_class->render = impress_document_render; +} + +static GdkPixbuf * +impress_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document, + EvRenderContext *rc, + gboolean border) +{ + GdkPixbuf *pixbuf; + GdkPixbuf *scaled_pixbuf; + + pixbuf = impress_document_render_pixbuf (EV_DOCUMENT (document), rc); + scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf, + (PAGE_WIDTH * rc->scale), + (PAGE_HEIGHT * rc->scale), + GDK_INTERP_BILINEAR); + g_object_unref (pixbuf); + + if (border) + { + GdkPixbuf *tmp_pixbuf = scaled_pixbuf; + + scaled_pixbuf = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf); + g_object_unref (tmp_pixbuf); + } + + return scaled_pixbuf; +} + +static void +impress_document_thumbnails_get_dimensions (EvDocumentThumbnails *document, + EvRenderContext *rc, + gint *width, + gint *height) +{ + gdouble page_width, page_height; + + impress_document_get_page_size (EV_DOCUMENT (document), + rc->page, + &page_width, &page_height); + + if (rc->rotation == 90 || rc->rotation == 270) + { + *width = (gint) (page_height * rc->scale); + *height = (gint) (page_width * rc->scale); + } + else + { + *width = (gint) (page_width * rc->scale); + *height = (gint) (page_height * rc->scale); + } +} + +static void +impress_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface) +{ + iface->get_thumbnail = impress_document_thumbnails_get_thumbnail; + iface->get_dimensions = impress_document_thumbnails_get_dimensions; +} + +static void +impress_document_init (ImpressDocument *impress_document) +{ + GdkWindow *window; + + impress_document->mutex = g_mutex_new (); + impress_document->ctx = imp_create_context(&imp_render_functions); + + window = gdk_screen_get_root_window (gdk_screen_get_default ()); + + impress_document->pixmap = gdk_pixmap_new (window, + PAGE_WIDTH, PAGE_HEIGHT, -1); + impress_document->gc = gdk_gc_new (impress_document->pixmap); + impress_document->pango_ctx = gdk_pango_context_get_for_screen (gdk_screen_get_default ()); +} + +/* + * vim: sw=2 ts=8 cindent noai bs=2 + */ diff --git a/backend/impress/impress-document.h b/backend/impress/impress-document.h new file mode 100644 index 00000000..17a3c19a --- /dev/null +++ b/backend/impress/impress-document.h @@ -0,0 +1,39 @@ + +/* pdfdocument.h: Implementation of EvDocument for impresss + * Copyright (C) 2005, Jonathan Blandford + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __IMPRESS_DOCUMENT_H__ +#define __IMPRESS_DOCUMENT_H__ + +#include "ev-document.h" + +G_BEGIN_DECLS + +#define IMPRESS_TYPE_DOCUMENT (impress_document_get_type ()) +#define IMPRESS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IMPRESS_TYPE_DOCUMENT, ImpressDocument)) +#define IMPRESS_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IMPRESS_TYPE_DOCUMENT)) + +typedef struct _ImpressDocument ImpressDocument; + +GType impress_document_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT GType register_evince_backend (GTypeModule *module); + +G_END_DECLS + +#endif /* __IMPRESS_DOCUMENT_H__ */ diff --git a/backend/impress/impressdocument.evince-backend.in b/backend/impress/impressdocument.evince-backend.in new file mode 100644 index 00000000..61de77ab --- /dev/null +++ b/backend/impress/impressdocument.evince-backend.in @@ -0,0 +1,4 @@ +[Evince Backend] +Module=impressdocument +_TypeDescription=Impress Slides +MimeType=application/vnd.sun.xml.impress;application/vnd.oasis.opendocument.presentation; diff --git a/backend/impress/internal.h b/backend/impress/internal.h new file mode 100644 index 00000000..eb99c3ec --- /dev/null +++ b/backend/impress/internal.h @@ -0,0 +1,85 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include "zip.h" + +#ifndef INTERNAL_H +#define INTERNAL_H + +struct ImpDoc_struct { + ikstack *stack; + zip *zfile; + iks *content; + iks *styles; + iks *meta; + ImpPage *pages; + ImpPage *last_page; + int nr_pages; + void (*get_geometry)(ImpRenderCtx *ctx); + void (*render_page)(ImpRenderCtx *ctx, void *drw_data); +}; + +struct ImpPage_struct { + struct ImpPage_struct *next; + struct ImpPage_struct *prev; + ImpDoc *doc; + iks *page; + const char *name; + int nr; +}; + +struct ImpRenderCtx_struct { + const ImpDrawer *drw; + ImpPage *page; + iks *content; + iks *styles; + iks *last_element; + int step; + int pix_w, pix_h; + double cm_w, cm_h; + double fact_x, fact_y; +}; + +char *r_get_style (ImpRenderCtx *ctx, iks *node, char *attr); +int r_get_color(ImpRenderCtx *ctx, iks *node, char *name, ImpColor *ic); +void r_parse_color(const char *color, ImpColor *ic); +int r_get_x (ImpRenderCtx *ctx, iks *node, char *name); +int r_get_y (ImpRenderCtx *ctx, iks *node, char *name); +int r_get_angle (iks *node, char *name, int def); + +enum { + IMP_LE_NONE = 0, + IMP_LE_ARROW, + IMP_LE_SQUARE, + IMP_LE_DIMENSION, + IMP_LE_DOUBLE_ARROW, + IMP_LE_SMALL_ARROW, + IMP_LE_ROUND_ARROW, + IMP_LE_SYM_ARROW, + IMP_LE_LINE_ARROW, + IMP_LE_ROUND_LARGE_ARROW, + IMP_LE_CIRCLE, + IMP_LE_SQUARE_45, + IMP_LE_CONCAVE_ARROW +}; + +void _imp_draw_rect(ImpRenderCtx *ctx, void *drw_data, int fill, int x, int y, int w, int h, int round); +void _imp_draw_line_end(ImpRenderCtx *ctx, void *drw_data, int type, int size, int x, int y, int x2, int y2); +void _imp_draw_image(ImpRenderCtx *ctx, void *drw_data, const char *name, int x, int y, int w, int h); +void _imp_tile_image(ImpRenderCtx *ctx, void *drw_data, const char *name, int x, int y, int w, int h); + +int _imp_fill_back(ImpRenderCtx *ctx, void *drw_data, iks *node); +void r_text(ImpRenderCtx *ctx, void *drw_data, iks *node); +void r_polygon(ImpRenderCtx *ctx, void *drw_data, iks *node); +void r_circle(ImpRenderCtx *ctx, void *drw_data, iks *node); +void r_polyline(ImpRenderCtx *ctx, void *drw_data, iks *node); +void r_draw_gradient (ImpRenderCtx *ctx, void *drw_data, iks *node); + +int _imp_oo13_load(ImpDoc *doc); +int _imp_oasis_load(ImpDoc *doc); + + +#endif /* INTERNAL_H */ diff --git a/backend/impress/r_back.c b/backend/impress/r_back.c new file mode 100644 index 00000000..3f050d9e --- /dev/null +++ b/backend/impress/r_back.c @@ -0,0 +1,46 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "internal.h" + +int +_imp_fill_back(ImpRenderCtx *ctx, void *drw_data, iks *node) +{ + ImpColor col; + char *type; + char *stil, *gfx; + iks *x; + + type = r_get_style(ctx, node, "draw:fill"); + if (type == 0) return 0; + + if (strcmp(type, "solid") == 0) { + if (r_get_color(ctx, node, "draw:fill-color", &col)) { + ctx->drw->set_fg_color(drw_data, &col); + } + ctx->drw->draw_rect(drw_data, 1, 0, 0, ctx->pix_w, ctx->pix_h); + } else if (strcmp (type, "bitmap") == 0) { + stil = r_get_style(ctx, node, "draw:fill-image-name"); + x = iks_find_with_attrib(iks_find(ctx->styles, "office:styles"), + "draw:fill-image", "draw:name", stil + ); + gfx = iks_find_attrib(x, "xlink:href"); + if (gfx) { + if (iks_strcmp(r_get_style(ctx, node, "style:repeat"), "stretch") == 0) { + _imp_draw_image(ctx, drw_data, gfx, 0, 0, ctx->pix_w, ctx->pix_h); + } else { + _imp_tile_image(ctx, drw_data, gfx, 0, 0, ctx->pix_w, ctx->pix_h); + } + } + } else if (strcmp(type, "gradient") == 0) { + r_draw_gradient(ctx, drw_data, node); + } else { + return 0; + } + return 1; +} diff --git a/backend/impress/r_draw.c b/backend/impress/r_draw.c new file mode 100644 index 00000000..aad1655e --- /dev/null +++ b/backend/impress/r_draw.c @@ -0,0 +1,120 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "internal.h" +#include + +void +_imp_draw_rect(ImpRenderCtx *ctx, void *drw_data, int fill, int x, int y, int w, int h, int round) +{ + int a; + + if (0 == round) { + ctx->drw->draw_rect(drw_data, fill, x, y, w, h); + return; + } + + ctx->drw->draw_arc(drw_data, fill, + x, y, round, round, 90, 90); + ctx->drw->draw_arc(drw_data, fill, + x + w - round, y, round, round, 0, 90); + ctx->drw->draw_arc(drw_data, fill, + x + w - round, y + h - round, round, round, 270, 90); + ctx->drw->draw_arc(drw_data, fill, + x, y + h - round, round, round, 180, 90); + + a = round / 2; + if (fill) { + ctx->drw->draw_rect(drw_data, 1, x + a, y, w - a - a, h); + ctx->drw->draw_rect(drw_data, 1, x, y + a, w, h - a - a); + return; + } + ctx->drw->draw_line(drw_data, x + a, y, x + w - a, y); + ctx->drw->draw_line(drw_data, x + a, y + h, x + w - a, y + h); + ctx->drw->draw_line(drw_data, x, y + a, x, y + h - a); + ctx->drw->draw_line(drw_data, x + w, y + a, x + w, y + h - a); +} + +void +_imp_draw_line_end(ImpRenderCtx *ctx, void *drw_data, int type, int size, int x, int y, int x2, int y2) +{ + ImpPoint pts[4]; + double ia, a; + + // FIXME: different types and sizes + + pts[0].x = x2; + pts[0].y = y2; + + ia = 20 * 3.14 * 2 / 360; + + if (x2-x == 0) { + if (y < y2) a = 3.14 + (3.14 / 2); else a = (3.14 / 2); + } else if (y2-y == 0) { + if (x < x2) a = 3.14; else a = 0; + } else + a = atan ((y2-y) / (x2-x)) - 3.14; + + pts[1].x = x2 + 0.3 * ctx->fact_x * cos (a - ia); + pts[1].y = y2 + 0.3 * ctx->fact_y * sin (a - ia); + + pts[2].x = x2 + 0.3 * ctx->fact_x * cos (a + ia); + pts[2].y = y2 + 0.3 * ctx->fact_y * sin (a + ia); + + ctx->drw->draw_polygon(drw_data, 1, pts, 3); +} + +void +_imp_draw_image(ImpRenderCtx *ctx, void *drw_data, const char *name, int x, int y, int w, int h) +{ + void *img1, *img2; + char *pix; + size_t len; + + len = zip_get_size(ctx->page->doc->zfile, name); + pix = malloc(len); + if (!pix) return; + zip_load(ctx->page->doc->zfile, name, pix); + + img1 = ctx->drw->open_image(drw_data, pix, len); + free(pix); + if (!img1) return; + img2 = ctx->drw->scale_image(drw_data, img1, w, h); + if (img2) { + ctx->drw->draw_image(drw_data, img2, x, y, w, h); + ctx->drw->close_image(drw_data, img2); + } + ctx->drw->close_image(drw_data, img1); +} + +void +_imp_tile_image(ImpRenderCtx *ctx, void *drw_data, const char *name, int x, int y, int w, int h) +{ + void *img1; + char *pix; + size_t len; + int gx, gy, gw, gh; + + len = zip_get_size(ctx->page->doc->zfile, name); + pix = malloc(len); + if (!pix) return; + zip_load(ctx->page->doc->zfile, name, pix); + + img1 = ctx->drw->open_image(drw_data, pix, len); + free(pix); + if (!img1) return; + + ctx->drw->get_image_size(drw_data, img1, &gw, &gh); + for (gx = x; gx < w; gx += gw) { + for (gy = y; gy < h; gy += gh) { + ctx->drw->draw_image(drw_data, img1, gx, gy, gw, gh); + } + } + + ctx->drw->close_image(drw_data, img1); +} diff --git a/backend/impress/r_geometry.c b/backend/impress/r_geometry.c new file mode 100644 index 00000000..4819821a --- /dev/null +++ b/backend/impress/r_geometry.c @@ -0,0 +1,208 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "internal.h" +#include + +void +r_parse_color(const char *color, ImpColor *ic) +{ + unsigned int cval; + + if (1 != sscanf(color, "#%X", &cval)) return; + + ic->red = (cval & 0xFF0000) >> 8; + ic->green = cval & 0x00FF00; + ic->blue = (cval & 0xFF) << 8; +} + +int +r_get_color(ImpRenderCtx *ctx, iks *node, char *name, ImpColor *ic) +{ + char *color; + + color = r_get_style(ctx, node, name); + if (!color) return 0; + r_parse_color(color, ic); + + return 1; +} + +static void +fg_color(ImpRenderCtx *ctx, void *drw_data, iks *node, char *name) +{ + ImpColor ic; + + if (r_get_color(ctx, node, name, &ic)) { + ctx->drw->set_fg_color(drw_data, &ic); + } +} + +int +r_get_x (ImpRenderCtx *ctx, iks *node, char *name) +{ + char *val; + + val = iks_find_attrib (node, name); + if (!val) return 0; + return atof (val) * ctx->fact_x; +} + +int +r_get_y (ImpRenderCtx *ctx, iks *node, char *name) +{ + char *val; + + val = iks_find_attrib (node, name); + if (!val) return 0; + return atof (val) * ctx->fact_y; +} + +int +r_get_angle (iks *node, char *name, int def) +{ + char *tmp; + + tmp = iks_find_attrib (node, name); + if (!tmp) return def; + return atof (tmp); +} + +static int x, y, w, h; +static int px, py, pw, ph; + +static void +r_get_viewbox (iks *node) +{ + char *tmp; + + tmp = iks_find_attrib (node, "svg:viewBox"); + if (!tmp) return; + sscanf (tmp, "%d %d %d %d", &px, &py, &pw, &ph); +} + +void +r_polygon(ImpRenderCtx *ctx, void *drw_data, iks *node) +{ + char *data; + ImpPoint *points; + int i, cnt, j; + int num; + int fill = 1; + + data = r_get_style (ctx, node, "draw:fill"); + if (!data || strcmp (data, "solid") != 0) fill = 0; + + x = r_get_x (ctx, node, "svg:x"); + y = r_get_y (ctx, node, "svg:y"); + w = r_get_x (ctx, node, "svg:width"); + h = r_get_y (ctx, node, "svg:height"); + r_get_viewbox (node); + + data = iks_find_attrib (node, "draw:points"); + points = malloc (sizeof (ImpPoint) * strlen (data) / 4); + + cnt = 0; + j = 0; + num = -1; + for (i = 0; data[i]; i++) { + if (data[i] >= '0' && data[i] <= '9') { + if (num == -1) num = i; + } else { + if (num != -1) { + if (j == 0) { + points[cnt].x = atoi (data + num); + j = 1; + } else { + points[cnt++].y = atoi (data + num); + j = 0; + } + num = -1; + } + } + } + if (num != -1) { + if (j == 0) { + points[cnt].x = atoi (data + num); + } else { + points[cnt++].y = atoi (data + num); + } + } + for (i = 0; i < cnt; i++) { + points[i].x = x + points[i].x * w / pw; + points[i].y = y + points[i].y * h / ph; + } + + if (fill) { + fg_color(ctx, drw_data, node, "draw:fill-color"); + ctx->drw->draw_polygon(drw_data, 1, points, cnt); + } + fg_color(ctx, drw_data, node, "svg:stroke-color"); + ctx->drw->draw_polygon(drw_data, 0, points, cnt); + + free (points); +} + +void +r_polyline(ImpRenderCtx *ctx, void *drw_data, iks *node) +{ + char *data; + ImpPoint *points; + int i, cnt, j; + int num; + int pen_x, pen_y; + + x = r_get_x (ctx, node, "svg:x"); + y = r_get_y (ctx, node, "svg:y"); + w = r_get_x (ctx, node, "svg:width"); + h = r_get_y (ctx, node, "svg:height"); + r_get_viewbox (node); + + data = iks_find_attrib (node, "draw:points"); + points = malloc (sizeof (ImpPoint) * strlen (data) / 4); + + cnt = 0; + j = 0; + num = -1; + for (i = 0; data[i]; i++) { + if (data[i] >= '0' && data[i] <= '9') { + if (num == -1) num = i; + } else { + if (num != -1) { + if (j == 0) { + points[cnt].x = atoi (data + num); + j = 1; + } else { + points[cnt++].y = atoi (data + num); + j = 0; + } + num = -1; + } + } + } + if (num != -1) { + if (j == 0) { + points[cnt].x = atoi (data + num); + } else { + points[cnt++].y = atoi (data + num); + } + } + + pen_x = x + points[0].x * w /pw; + pen_y = y + points[0].y * h / ph; + fg_color(ctx, drw_data, node, "svg:stroke-color"); + for (i = 1; i < cnt; i++) { + int tx, ty; + tx = x + points[i].x * w / pw; + ty = y + points[i].y * h / ph; + ctx->drw->draw_line(drw_data, pen_x, pen_y, tx, ty); + pen_x = tx; + pen_y = ty; + } + free (points); +} diff --git a/backend/impress/r_gradient.c b/backend/impress/r_gradient.c new file mode 100644 index 00000000..79636fe2 --- /dev/null +++ b/backend/impress/r_gradient.c @@ -0,0 +1,387 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "internal.h" +#include + +#define GRAD_LINEAR 0 +#define GRAD_AXIAL 1 +#define GRAD_SQUARE 2 +#define GRAD_RECTANGULAR 3 +#define GRAD_RADIAL 4 +#define GRAD_ELLIPTICAL 5 + +typedef struct Gradient_s { + int type; + ImpColor start; + int start_intensity; + ImpColor end; + int end_intensity; + int angle; + int border; + int steps; + int offset_x; + int offset_y; +} Gradient; + +typedef struct Rectangle_s { + int Left; + int Top; + int Right; + int Bottom; +} Rectangle; + +static void +poly_rotate (ImpPoint *poly, int n, int cx, int cy, double fAngle) +{ + int i; + long nX, nY; + + for (i = 0; i < n; i++) { + nX = poly->x - cx; + nY = poly->y - cy; + poly->x = (cos(fAngle) * nX + sin(fAngle) * nY) + cx; + poly->y = - (sin(fAngle)* nX - cos(fAngle) * nY) + cy; + poly++; + } +} + +static void +r_draw_gradient_simple (ImpRenderCtx *ctx, void *drw_data, Gradient *grad) +{ + Rectangle rRect = { 0, 0, ctx->pix_w - 1, ctx->pix_h - 1 }; + Rectangle aRect, aFullRect; + ImpPoint poly[4], tempoly[2]; + ImpColor gcol; + double fW, fH, fDX, fDY, fAngle; + double fScanLine, fScanInc; + long redSteps, greenSteps, blueSteps; + long nBorder; + int i, nSteps, nSteps2; + int cx, cy; + + cx = rRect.Left + (rRect.Right - rRect.Left) / 2; + cy = rRect.Top + (rRect.Bottom - rRect.Top) / 2; + + aRect = rRect; + aRect.Top--; aRect.Left--; aRect.Bottom++; aRect.Right++; + fW = rRect.Right - rRect.Left; + fH = rRect.Bottom - rRect.Top; + fAngle = (((double) grad->angle) * 3.14 / 1800.0); + fDX = fW * fabs (cos (fAngle)) + fH * fabs (sin (fAngle)); + fDY = fH * fabs (cos (fAngle)) + fW * fabs (sin (fAngle)); + fDX = (fDX - fW) * 0.5 - 0.5; + fDY = (fDY - fH) * 0.5 - 0.5; + aRect.Left -= fDX; + aRect.Right += fDX; + aRect.Top -= fDY; + aRect.Bottom += fDY; + aFullRect = aRect; + + nBorder = grad->border * (aRect.Bottom - aRect.Top) / 100; + if (grad->type == GRAD_LINEAR) { + aRect.Top += nBorder; + } else { + nBorder >>= 1; + aRect.Top += nBorder; + aRect.Bottom -= nBorder; + } + + if (aRect.Top > (aRect.Bottom - 1)) + aRect.Top = aRect.Bottom - 1; + + poly[0].x = aFullRect.Left; + poly[0].y = aFullRect.Top; + poly[1].x = aFullRect.Right; + poly[1].y = aFullRect.Top; + poly[2].x = aRect.Right; + poly[2].y = aRect.Top; + poly[3].x = aRect.Left; + poly[3].y = aRect.Top; + poly_rotate (&poly[0], 4, cx, cy, fAngle); + + redSteps = grad->end.red - grad->start.red; + greenSteps = grad->end.green - grad->start.green; + blueSteps = grad->end.blue - grad->start.blue; + nSteps = grad->steps; + if (nSteps == 0) { + long mr; + mr = aRect.Bottom - aRect.Top; + if (mr < 50) + nSteps = mr / 2; + else + nSteps = mr / 4; + mr = abs(redSteps); + if (abs(greenSteps) > mr) mr = abs(greenSteps); + if (abs(blueSteps) > mr) mr = abs(blueSteps); + if (mr < nSteps) nSteps = mr; + } + + if (grad->type == GRAD_AXIAL) { + if (nSteps & 1) nSteps++; + nSteps2 = nSteps + 2; + gcol = grad->end; + redSteps <<= 1; + greenSteps <<= 1; + blueSteps <<= 1; + } else { + nSteps2 = nSteps + 1; + gcol = grad->start; + } + + fScanLine = aRect.Top; + fScanInc = (double)(aRect.Bottom - aRect.Top) / (double)nSteps; + + for (i = 0; i < nSteps2; i++) { + // draw polygon + ctx->drw->set_fg_color(drw_data, &gcol); + ctx->drw->draw_polygon(drw_data, 1, &poly[0], 4); + // calc next polygon + aRect.Top = (long)(fScanLine += fScanInc); + if (i == nSteps) { + tempoly[0].x = aFullRect.Left; + tempoly[0].y = aFullRect.Bottom; + tempoly[1].x = aFullRect.Right; + tempoly[1].y = aFullRect.Bottom; + } else { + tempoly[0].x = aRect.Left; + tempoly[0].y = aRect.Top; + tempoly[1].x = aRect.Right; + tempoly[1].y = aRect.Top; + } + poly_rotate (&tempoly[0], 2, cx, cy, fAngle); + poly[0] = poly[3]; + poly[1] = poly[2]; + poly[2] = tempoly[1]; + poly[3] = tempoly[0]; + // calc next color + if (grad->type == GRAD_LINEAR) { + gcol.red = grad->start.red + ((redSteps * i) / nSteps2); + gcol.green = grad->start.green + ((greenSteps * i) / nSteps2); + gcol.blue = grad->start.blue + ((blueSteps * i) / nSteps2); + } else { + if (i >= nSteps) { + gcol.red = grad->end.red; + gcol.green = grad->end.green; + gcol.blue = grad->end.blue; + } else { + if (i <= (nSteps / 2)) { + gcol.red = grad->end.red - ((redSteps * i) / nSteps2); + gcol.green = grad->end.green - ((greenSteps * i) / nSteps2); + gcol.blue = grad->end.blue - ((blueSteps * i) / nSteps2); + } else { + int i2 = i - nSteps / 2; + gcol.red = grad->start.red + ((redSteps * i2) / nSteps2); + gcol.green = grad->start.green + ((greenSteps * i2) / nSteps2); + gcol.blue = grad->start.blue + ((blueSteps * i2) / nSteps2); + } + } + } + } +} + +static void +r_draw_gradient_complex (ImpRenderCtx *ctx, void *drw_data, Gradient *grad) +{ + Rectangle rRect = { 0, 0, ctx->pix_w - 1, ctx->pix_h - 1 }; + Rectangle aRect = rRect; + ImpColor gcol; + ImpPoint poly[4]; + double fAngle = (((double) grad->angle) * 3.14 / 1800.0); + long redSteps, greenSteps, blueSteps; + long nZW, nZH; + long bX, bY; + long sW, sH; + long cx, cy; + int i; + long nSteps; + double sTop, sLeft, sRight, sBottom, sInc; + int minRect; + + redSteps = grad->end.red - grad->start.red; + greenSteps = grad->end.green - grad->start.green; + blueSteps = grad->end.blue - grad->start.blue; + + if (grad->type == GRAD_SQUARE || grad->type == GRAD_RECTANGULAR) { + double fW = aRect.Right - aRect.Left; + double fH = aRect.Bottom - aRect.Top; + double fDX = fW * fabs (cos (fAngle)) + fH * fabs (sin (fAngle)); + double fDY = fH * fabs (cos (fAngle)) + fW * fabs (sin (fAngle)); + fDX = (fDX - fW) * 0.5 - 0.5; + fDY = (fDY - fH) * 0.5 - 0.5; + aRect.Left -= fDX; + aRect.Right += fDX; + aRect.Top -= fDY; + aRect.Bottom += fDY; + } + + sW = aRect.Right - aRect.Left; + sH = aRect.Bottom - aRect.Top; + + if (grad->type == GRAD_SQUARE) { + if (sW > sH) sH = sW; else sW = sH; + } else if (grad->type == GRAD_RADIAL) { + sW = 0.5 + sqrt ((double)sW*(double)sW + (double)sH*(double)sH); + sH = sW; + } else if (grad->type == GRAD_ELLIPTICAL) { + sW = 0.5 + (double)sW * 1.4142; + sH = 0.5 + (double)sH * 1.4142; + } + + nZW = (aRect.Right - aRect.Left) * grad->offset_x / 100; + nZH = (aRect.Bottom - aRect.Top) * grad->offset_y / 100; + bX = grad->border * sW / 100; + bY = grad->border * sH / 100; + cx = aRect.Left + nZW; + cy = aRect.Top + nZH; + + sW -= bX; + sH -= bY; + + aRect.Left = cx - ((aRect.Right - aRect.Left) >> 1); + aRect.Top = cy - ((aRect.Bottom - aRect.Top) >> 1); + + nSteps = grad->steps; + minRect = aRect.Right - aRect.Left; + if (aRect.Bottom - aRect.Top < minRect) minRect = aRect.Bottom - aRect.Top; + if (nSteps == 0) { + long mr; + if (minRect < 50) + nSteps = minRect / 2; + else + nSteps = minRect / 4; + mr = abs(redSteps); + if (abs(greenSteps) > mr) mr = abs(greenSteps); + if (abs(blueSteps) > mr) mr = abs(blueSteps); + if (mr < nSteps) nSteps = mr; + } + + sLeft = aRect.Left; + sTop = aRect.Top; + sRight = aRect.Right; + sBottom = aRect.Bottom; + sInc = (double) minRect / (double) nSteps * 0.5; + + gcol = grad->start; + poly[0].x = rRect.Left; + poly[0].y = rRect.Top; + poly[1].x = rRect.Right; + poly[1].y = rRect.Top; + poly[2].x = rRect.Right; + poly[2].y = rRect.Bottom; + poly[3].x = rRect.Left; + poly[3].y = rRect.Bottom; + ctx->drw->set_fg_color(drw_data, &gcol); + ctx->drw->draw_polygon(drw_data, 1, &poly[0], 4); + + for (i = 0; i < nSteps; i++) { + aRect.Left = (long) (sLeft += sInc); + aRect.Top = (long) (sTop += sInc); + aRect.Right = (long) (sRight -= sInc); + aRect.Bottom = (long) (sBottom -= sInc); + if (aRect.Bottom - aRect.Top < 2 || aRect.Right - aRect.Left < 2) + break; + + gcol.red = grad->start.red + (redSteps * (i+1) / nSteps); + gcol.green = grad->start.green + (greenSteps * (i+1) / nSteps); + gcol.blue = grad->start.blue + (blueSteps * (i+1) / nSteps); + ctx->drw->set_fg_color(drw_data, &gcol); + + if (grad->type == GRAD_RADIAL || grad->type == GRAD_ELLIPTICAL) { + ctx->drw->draw_arc(drw_data, 1, aRect.Left, aRect.Top, + aRect.Right - aRect.Left, aRect.Bottom - aRect.Top, + 0, 360); + } else { + poly[0].x = aRect.Left; + poly[0].y = aRect.Top; + poly[1].x = aRect.Right; + poly[1].y = aRect.Top; + poly[2].x = aRect.Right; + poly[2].y = aRect.Bottom; + poly[3].x = aRect.Left; + poly[3].y = aRect.Bottom; + poly_rotate (&poly[0], 4, cx, cy, fAngle); + ctx->drw->draw_polygon(drw_data, 1, &poly[0], 4); + } + } +} + +void +r_draw_gradient (ImpRenderCtx *ctx, void *drw_data, iks *node) +{ +// GdkGC *gc; + Gradient grad; + char *stil, *tmp; + iks *x; + + stil = r_get_style (ctx, node, "draw:fill-gradient-name"); + x = iks_find_with_attrib (iks_find (ctx->styles, "office:styles"), + "draw:gradient", "draw:name", stil); + if (x) { + memset (&grad, 0, sizeof (Gradient)); + grad.type = -1; + grad.offset_x = 50; + grad.offset_y = 50; + + tmp = iks_find_attrib (x, "draw:start-color"); + if (tmp) r_parse_color (tmp, &grad.start); + tmp = iks_find_attrib (x, "draw:start-intensity"); + if (tmp) { + int val = atoi (tmp); + grad.start.red = grad.start.red * val / 100; + grad.start.green = grad.start.green * val / 100; + grad.start.blue = grad.start.blue * val / 100; + } + tmp = iks_find_attrib (x, "draw:end-color"); + if (tmp) r_parse_color (tmp, &grad.end); + tmp = iks_find_attrib (x, "draw:end-intensity"); + if (tmp) { + int val = atoi (tmp); + grad.end.red = grad.end.red * val / 100; + grad.end.green = grad.end.green * val / 100; + grad.end.blue = grad.end.blue * val / 100; + } + tmp = iks_find_attrib (x, "draw:angle"); + if (tmp) grad.angle = atoi(tmp) % 3600; + tmp = iks_find_attrib (x, "draw:border"); + if (tmp) grad.border = atoi(tmp); + tmp = r_get_style (ctx, node, "draw:gradient-step-count"); + if (tmp) grad.steps = atoi (tmp); + tmp = iks_find_attrib (x, "draw:cx"); + if (tmp) grad.offset_x = atoi (tmp); + tmp = iks_find_attrib (x, "draw:cy"); + if (tmp) grad.offset_y = atoi (tmp); + tmp = iks_find_attrib (x, "draw:style"); + if (iks_strcmp (tmp, "linear") == 0) + grad.type = GRAD_LINEAR; + else if (iks_strcmp (tmp, "axial") == 0) + grad.type = GRAD_AXIAL; + else if (iks_strcmp (tmp, "radial") == 0) + grad.type = GRAD_RADIAL; + else if (iks_strcmp (tmp, "rectangular") == 0) + grad.type = GRAD_RECTANGULAR; + else if (iks_strcmp (tmp, "ellipsoid") == 0) + grad.type = GRAD_ELLIPTICAL; + else if (iks_strcmp (tmp, "square") == 0) + grad.type = GRAD_SQUARE; + + if (grad.type == -1) return; + +// gc = ctx->gc; +// ctx->gc = gdk_gc_new (ctx->d); +// gdk_gc_copy (ctx->gc, gc); + + if (grad.type == GRAD_LINEAR || grad.type == GRAD_AXIAL) + r_draw_gradient_simple (ctx, drw_data, &grad); + else + r_draw_gradient_complex (ctx, drw_data, &grad); + +// g_object_unref (ctx->gc); +// ctx->gc = gc; + } +} diff --git a/backend/impress/r_style.c b/backend/impress/r_style.c new file mode 100644 index 00000000..39ee9c62 --- /dev/null +++ b/backend/impress/r_style.c @@ -0,0 +1,111 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "internal.h" + +static char * +get_style(ImpRenderCtx *ctx, iks *node, char *style, char *attr) +{ + char *ret; + iks *x; + + if (!style) return NULL; + + if (iks_root (node) == ctx->content) { + x = iks_find_with_attrib (iks_find (ctx->content, "office:automatic-styles"), + "style:style", "style:name", style); + } else { + x = iks_find_with_attrib (iks_find (ctx->styles, "office:automatic-styles"), + "style:style", "style:name", style); + } + if (!x) return NULL; + + while (x) { + ret = iks_find_attrib (iks_find (x, "style:properties"), attr); + if (ret) return ret; + ret = iks_find_attrib (iks_find (x, "style:text-properties"), attr); + if (ret) return ret; + ret = iks_find_attrib (iks_find (x, "style:paragraph-properties"), attr); + if (ret) return ret; + ret = iks_find_attrib (iks_find (x, "style:graphic-properties"), attr); + if (ret) return ret; + ret = iks_find_attrib (iks_find (x, "style:drawing-page-properties"), attr); + if (ret) return ret; + + style = iks_find_attrib (x, "style:parent-style-name"); + if (!style) return NULL; + + x = iks_find_with_attrib (iks_find (ctx->styles, "office:styles"), + "style:style", "style:name", style); + + } + return NULL; +} + +char * +r_get_style (ImpRenderCtx *ctx, iks *node, char *attr) +{ + char *ret, *s; + iks *x; + + ret = iks_find_attrib (node, attr); + if (ret) return ret; + + for (x = node; x; x = iks_parent (x)) { + s = iks_find_attrib (x, "text:style-name"); + ret = get_style (ctx, node, s, attr); + if (ret) return ret; + s = iks_find_attrib (x, "presentation:style-name"); + ret = get_style (ctx, node, s, attr); + if (ret) return ret; + s = iks_find_attrib (x, "draw:style-name"); + ret = get_style (ctx, node, s, attr); + if (ret) return ret; + } + return NULL; +} + +#if 0 +static iks * +get_style_x (ImpRenderCtx *ctx, iks *node, char *style, char *attr) +{ + iks *x; + + if (!style) return NULL; + + if (iks_root (node) == ctx->content) { + x = iks_find_with_attrib (iks_find (ctx->content, "office:automatic-styles"), + "text:list-style", "style:name", style); + } else { + x = iks_find_with_attrib (iks_find (ctx->styles, "office:automatic-styles"), + "text:list-style", "style:name", style); + } + return x; +} + +static iks * +r_get_bullet (ImpRenderCtx *ctx, iks *node, char *attr) +{ + iks *ret; + char *s; + iks *x; + + for (x = node; x; x = iks_parent (x)) { + s = iks_find_attrib (x, "text:style-name"); + ret = get_style_x (ctx, node, s, attr); + if (ret) return ret; + s = iks_find_attrib (x, "presentation:style-name"); + ret = get_style_x (ctx, node, s, attr); + if (ret) return ret; + s = iks_find_attrib (x, "draw:style-name"); + ret = get_style_x (ctx, node, s, attr); + if (ret) return ret; + } + return NULL; +} +#endif diff --git a/backend/impress/r_text.c b/backend/impress/r_text.c new file mode 100644 index 00000000..4b89bcad --- /dev/null +++ b/backend/impress/r_text.c @@ -0,0 +1,386 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "internal.h" + +struct Span { + struct Span *next; + int x, y; + int w, h; + char *text; + int len; + int size; + int styles; + ImpColor fg; +}; + +struct Line { + struct Line *next; + struct Span *spans; + struct Span *last_span; + int x, y; + int w, h; +}; + +struct Layout { + ikstack *s; + int x, y, w, h; + int tw, th; + struct Line *lines; + struct Line *last_line; + char spaces[128]; +}; + +static struct Line * +add_line(struct Layout *lay) +{ + struct Line *line; + + line = iks_stack_alloc(lay->s, sizeof(struct Line)); + memset(line, 0, sizeof(struct Line)); + + if (!lay->lines) lay->lines = line; + if (lay->last_line) lay->last_line->next = line; + lay->last_line = line; + + return line; +} + +static struct Span * +add_span(struct Layout *lay, char *text, int len, int size, int styles) +{ + struct Line *line; + struct Span *span; + + span = iks_stack_alloc(lay->s, sizeof(struct Span)); + memset(span, 0, sizeof(struct Span)); + span->text = text; + span->len = len; + span->size = size; + span->styles = styles; + + line = lay->last_line; + if (!line) line = add_line(lay); + if (line->spans) { + span->x = line->last_span->x + line->last_span->w; + span->y = line->last_span->y; + } else { + span->x = line->x; + span->y = line->y; + } + + if (!line->spans) line->spans = span; + if (line->last_span) line->last_span->next = span; + line->last_span = span; + + return span; +} + +static void +calc_sizes(ImpRenderCtx *ctx, void *drw_data, struct Layout *lay) +{ + struct Line *line; + struct Span *span; + + for (line = lay->lines; line; line = line->next) { + for (span = line->spans; span; span = span->next) { + ctx->drw->get_text_size(drw_data, + span->text, span->len, + span->size, span->styles, + &span->w, &span->h + ); + line->w += span->w; + if (span->h > line->h) line->h = span->h; + } + if (line->w > lay->tw) lay->tw = line->w; + lay->th += line->h; + } +} + +static void +calc_pos(ImpRenderCtx *ctx, struct Layout *lay) +{ + struct Line *line; + struct Span *span; + int x, y, x2; + + x = lay->x; + y = lay->y; + for (line = lay->lines; line; line = line->next) { + line->x = x; + line->y = y; + y += line->h; + x2 = x; + for (span = line->spans; span; span = span->next) { + span->x = x2; + span->y = y; + x2 += span->w; + } + } +} + +static void +_imp_draw_layout(ImpRenderCtx *ctx, void *drw_data, struct Layout *lay) +{ + struct Line *line; + struct Span *span; + + for (line = lay->lines; line; line = line->next) { + for (span = line->spans; span; span = span->next) { + ctx->drw->set_fg_color(drw_data, &span->fg); + ctx->drw->draw_text(drw_data, + span->x, span->y, + span->text, span->len, + span->size, + span->styles + ); + } + } +} + +static void +text_span(ImpRenderCtx *ctx, struct Layout *lay, iks *node, char *text, size_t len) +{ + struct Span *span; + double cm; + char *attr, *t, *s; + int px = 0, cont = 1; + int styles = IMP_NORMAL; + + attr = r_get_style(ctx, node, "fo:font-size"); + if (attr) { + cm = atof(attr); + if (strstr(attr, "pt")) cm = cm * 2.54 / 102; + px = cm * ctx->fact_y; + } + attr = r_get_style(ctx, node, "fo:font-weight"); + if (attr && strcmp(attr, "bold") == 0) styles |= IMP_BOLD; + attr = r_get_style(ctx, node, "style:text-underline"); + if (attr && strcmp(attr, "single") == 0) styles |= IMP_UNDERLINE; + attr = r_get_style(ctx, node, "fo:font-style"); + if (attr && strcmp(attr, "italic") == 0) styles |= IMP_ITALIC; + + t = text; + while (cont) { + s = strchr(t, '\n'); + if (s) { + int len2 = s - t; + span = add_span(lay, t, len2, px, styles); + t = s + 1; + len -= len2; + add_line(lay); + } else { + span = add_span(lay, text, len, px, styles); + cont = 0; + } + r_get_color(ctx, node, "fo:color", &span->fg); + } +} + +static void +text_p(ImpRenderCtx *ctx, struct Layout *lay, iks *node) +{ + iks *n, *n2; + + add_line(lay); + for (n = iks_child(node); n; n = iks_next(n)) { + if (iks_type(n) == IKS_CDATA) { + text_span(ctx, lay, node, iks_cdata(n), iks_cdata_size(n)); + } else if (iks_strcmp(iks_name(n), "text:span") == 0) { + for (n2 = iks_child(n); n2; n2 = iks_next(n2)) { + if (iks_type(n2) == IKS_CDATA) { + text_span(ctx, lay, n2, iks_cdata(n2), iks_cdata_size(n2)); + } else if (iks_strcmp(iks_name(n2), "text:s") == 0) { + char *attr; + int c = 1; + attr = iks_find_attrib(n2, "text:c"); + if (attr) c = atoi(attr); + if (c > 127) { + c = 127; + puts("bork bork"); + } + text_span(ctx, lay, n, lay->spaces, c); + } else if (iks_strcmp(iks_name(n2), "text:a") == 0) { + text_span(ctx, lay, n, iks_cdata(iks_child(n2)), iks_cdata_size(iks_child(n2))); + } else if (iks_strcmp(iks_name(n2), "text:tab-stop") == 0) { + text_span(ctx, lay, n, "\t", 1); + } else if (iks_strcmp(iks_name(n2), "text:page-number") == 0) { + char buf[8]; + sprintf(buf, "%d", ctx->page->nr); + text_span(ctx, lay, n, iks_stack_strdup(lay->s, buf, 0), strlen(buf)); + } + } + } else if (iks_strcmp(iks_name(n), "text:line-break") == 0) { + add_line(lay); + } else if (iks_strcmp(iks_name(n), "text:a") == 0) { + text_span(ctx, lay, n, iks_cdata(iks_child(n)), iks_cdata_size(iks_child(n))); + } else if (iks_strcmp(iks_name(n), "text:page-number") == 0) { + char buf[8]; + sprintf(buf, "%d", ctx->page->nr); + text_span(ctx, lay, n, iks_stack_strdup(lay->s, buf, 0), strlen(buf)); + } + } +} + +static void +text_list(ImpRenderCtx *ctx, struct Layout *lay, iks *node) +{ + iks *n, *n2; + + for (n = iks_first_tag(node); n; n = iks_next_tag(n)) { + for (n2 = iks_first_tag(n); n2; n2 = iks_next_tag(n2)) { + if (strcmp(iks_name(n2), "text:p") == 0) { + text_p(ctx, lay, n2); + } else if (strcmp(iks_name(n2), "text:ordered-list") == 0) { + text_list(ctx, lay, n2); + } else if (strcmp(iks_name(n2), "text:unordered-list") == 0) { + text_list(ctx, lay, n2); + } else if (strcmp(iks_name(n2), "text:list") == 0) { + text_list(ctx, lay, n2); + } + } + } +} + +void +r_text(ImpRenderCtx *ctx, void *drw_data, iks *node) +{ + struct Layout lay; + iks *n; + + memset(&lay, 0, sizeof(struct Layout)); + memset(&lay.spaces, ' ', 128); + lay.s = iks_stack_new(sizeof(struct Span) * 16, 0); + lay.x = r_get_x(ctx, node, "svg:x"); + lay.y = r_get_y(ctx, node, "svg:y"); + lay.w = r_get_y(ctx, node, "svg:width"); + lay.h = r_get_y(ctx, node, "svg:height"); + + for (n = iks_first_tag(node); n; n = iks_next_tag(n)) { + if (strcmp(iks_name(n), "text:p") == 0) { + text_p(ctx, &lay, n); + } else if (strcmp(iks_name(n), "text:ordered-list") == 0) { + text_list(ctx, &lay, n); + } else if (strcmp(iks_name(n), "text:unordered-list") == 0) { + text_list(ctx, &lay, n); + } else if (strcmp(iks_name(n), "text:list") == 0) { + text_list(ctx, &lay, n); + } + } + + calc_sizes(ctx, drw_data, &lay); + calc_pos(ctx, &lay); + _imp_draw_layout(ctx, drw_data, &lay); + + iks_stack_delete(lay.s); +} +/* +static void +text_span (render_ctx *ctx, text_ctx *tc, struct layout_s *lout, iks *node, char *text, int len) +{ + if (tc->bullet_flag && tc->bullet_sz) size = tc->bullet_sz; else size = r_get_font_size (ctx, tc, node); +} + +static int +is_animated (render_ctx *ctx, text_ctx *tc, iks *node) +{ + if (!ctx->step_mode) return 0; + if (!tc->id) return 0; + while (strcmp (iks_name (node), "draw:page") != 0 + && strcmp (iks_name (node), "style:master-page") != 0) + node = iks_parent (node); + node = iks_find (node, "presentation:animations"); + if (!node) return 0; + if (iks_find_with_attrib (node, "presentation:show-text", "draw:shape-id", tc->id)) return 1; + return 0; +} + +static void +text_p (render_ctx *ctx, text_ctx *tc, iks *node) +{ + if (is_animated (ctx, tc, node) && ctx->step_cnt >= ctx->step) lout->flag = 0; + ctx->step_cnt++; + + attr = r_get_style (ctx, node, "text:enable-numbering"); + if (attr && strcmp (attr, "true") == 0) { + if (iks_child (node) && tc->bullet) { + tc->bullet_flag = 1; + text_span (ctx, tc, lout, node, tc->bullet, strlen (tc->bullet)); + text_span (ctx, tc, lout, node, " ", 1); + tc->bullet_flag = 0; + } + } + + if (!lout->text) { +lout->h = 0; +attr = r_get_style (ctx, node, "fo:line-height"); +if (attr) { + int ratio = atoi (attr); + lout->lh = ratio; +} else { + lout->lh = 100; +} +tc->layouts = g_list_append (tc->layouts, lout); +// g_object_unref (lout->play); +// iks_stack_delete (s); + return; + } + + attr = r_get_style (ctx, node, "fo:text-align"); + if (attr) { + if (strcmp (attr, "center") == 0) + pango_layout_set_alignment (lout->play, PANGO_ALIGN_CENTER); + else if (strcmp (attr, "end") == 0) + pango_layout_set_alignment (lout->play, PANGO_ALIGN_RIGHT); + } + pango_layout_set_width (lout->play, tc->w * PANGO_SCALE); + pango_layout_set_markup (lout->play, lout->text, lout->text_len); + pango_layout_get_pixel_size (lout->play, &lout->w, &lout->h); + attr = r_get_style (ctx, node, "fo:line-height"); + if (attr) { + int ratio = atoi (attr); + lout->lh = ratio; + } else { + lout->lh = 100; + } + tc->layouts = g_list_append (tc->layouts, lout); +} + +static void +find_bullet (render_ctx *ctx, text_ctx *tc, iks *node) +{ + iks *x; + char *t; + x = r_get_bullet (ctx, node, "text:list-level-style-bullet"); + x = iks_find (x, "text:list-level-style-bullet"); + t = iks_find_attrib (x, "text:bullet-char"); + if (t) tc->bullet = t; else tc->bullet = "*"; + x = iks_find (x, "style:properties"); + t = iks_find_attrib (x, "fo:font-size"); + if (t) tc->bullet_sz = tc->last_sz * atoi (t) / 100; + else tc->bullet_sz = 0; +} + +void +r_text (render_ctx *ctx, iks *node) +{ + tc.id = iks_find_attrib (node, "draw:id"); + ctx->step_cnt = 0; + for (n = iks_first_tag (node); n; n = iks_next_tag (n)) { + if (strcmp (iks_name (n), "text:p") == 0) { + text_p (ctx, &tc, n); + } else if (strcmp (iks_name (n), "text:ordered-list") == 0) { + text_list (ctx, &tc, n); + } else if (strcmp (iks_name (n), "text:unordered-list") == 0) { + find_bullet (ctx, &tc, n); + text_list (ctx, &tc, n); + tc.bullet = 0; + } + } + +*/ diff --git a/backend/impress/render.c b/backend/impress/render.c new file mode 100644 index 00000000..67b80756 --- /dev/null +++ b/backend/impress/render.c @@ -0,0 +1,54 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "internal.h" + +ImpRenderCtx * +imp_create_context(const ImpDrawer *drw) +{ + ImpRenderCtx *ctx; + + ctx = calloc(1, sizeof(ImpRenderCtx)); + if (!ctx) return NULL; + ctx->drw = drw; + return ctx; +} + +void +imp_context_set_page(ImpRenderCtx *ctx, ImpPage *page) +{ + ctx->page = page; + ctx->content = page->doc->content; + ctx->styles = page->doc->styles; +} + +void +imp_context_set_step(ImpRenderCtx *ctx, int step) +{ + ctx->step = step; +} + +void +imp_render(ImpRenderCtx *ctx, void *drw_data) +{ + // find drawing area size + ctx->drw->get_size(drw_data, &ctx->pix_w, &ctx->pix_h); + // find page size + ctx->page->doc->get_geometry(ctx); + // calculate ratio + ctx->fact_x = ctx->pix_w / ctx->cm_w; + ctx->fact_y = ctx->pix_h / ctx->cm_h; + // call renderer + ctx->page->doc->render_page(ctx, drw_data); +} + +void +imp_delete_context(ImpRenderCtx *ctx) +{ + free(ctx); +} diff --git a/backend/impress/zip.c b/backend/impress/zip.c new file mode 100644 index 00000000..b1f25c8c --- /dev/null +++ b/backend/impress/zip.c @@ -0,0 +1,349 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +#include +#include "common.h" +#include "zip.h" +#include +#define _(x) x + +typedef unsigned long ulong; + +enum { + ZIP_OK = 0, + ZIP_NOMEM, + ZIP_NOSIG, + ZIP_BADZIP, + ZIP_NOMULTI, + ZIP_EOPEN, + ZIP_EREAD, + ZIP_NOFILE +}; + +struct zipfile { + struct zipfile *next; + char *name; + ulong crc; + ulong zip_size; + ulong real_size; + ulong pos; +}; + +struct zip_struct { + FILE *f; + struct zipfile *files; + ulong cd_pos; + ulong cd_size; + ulong cd_offset; + ulong head_size; + ulong rem_size; + ulong nr_files; +}; + +char * +zip_error (int err) +{ + char *ret; + + switch (err) { + case ZIP_OK: + ret = _("No error"); + break; + case ZIP_NOMEM: + ret = _("Not enough memory"); + break; + case ZIP_NOSIG: + ret = _("Cannot find ZIP signature"); + break; + case ZIP_BADZIP: + ret = _("Invalid ZIP file"); + break; + case ZIP_NOMULTI: + ret = _("Multi file ZIPs are not supported"); + break; + case ZIP_EOPEN: + ret = _("Cannot open the file"); + break; + case ZIP_EREAD: + ret = _("Cannot read data from file"); + break; + case ZIP_NOFILE: + ret = _("Cannot find file in the ZIP archive"); + break; + default: + ret = _("Unknown error"); + break; + } + return ret; +} + +static int +find_cd (zip *z) +{ + FILE *f; + char *buf; + ulong size, pos, i, flag; + + f = z->f; + if (fseek (f, 0, SEEK_END) != 0) return 1; + size = ftell (f); + if (size < 0xffff) pos = 0; else pos = size - 0xffff; + buf = malloc (size - pos + 1); + if (!buf) return 1; + if (fseek (f, pos, SEEK_SET) != 0) { + free (buf); + return 1; + } + if (fread (buf, size - pos, 1, f) != 1) { + free (buf); + return 1; + } + flag = 0; + for (i = size - pos - 3; i > 0; i--) { + if (buf[i] == 0x50 && buf[i+1] == 0x4b && buf[i+2] == 0x05 && buf[i+3] == 0x06) { + z->cd_pos = i + pos; + flag = 1; + break; + } + } + free (buf); + if (flag != 1) return 1; + return 0; +} + +static unsigned long +get_long (unsigned char *buf) +{ + return buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); +} + +static unsigned long +get_word (unsigned char *buf) +{ + return buf[0] + (buf[1] << 8); +} + +static int +list_files (zip *z) +{ + unsigned char buf[46]; + struct zipfile *zfile; + ulong pat, fn_size; + int nr = 0; + + pat = z->cd_offset; + while (nr < z->nr_files) { + fseek (z->f, pat + z->head_size, SEEK_SET); + + if (fread (buf, 46, 1, z->f) != 1) return ZIP_EREAD; + if (get_long (buf) != 0x02014b50) return ZIP_BADZIP; + + zfile = malloc (sizeof (struct zipfile)); + if (!zfile) return ZIP_NOMEM; + memset (zfile, 0, sizeof (struct zipfile)); + + zfile->crc = get_long (buf + 16); + zfile->zip_size = get_long (buf + 20); + zfile->real_size = get_long (buf + 24); + fn_size = get_word (buf + 28); + zfile->pos = get_long (buf + 42); + + zfile->name = malloc (fn_size + 1); + if (!zfile->name) { + free (zfile); + return ZIP_NOMEM; + } + fread (zfile->name, fn_size, 1, z->f); + zfile->name[fn_size] = '\0'; + + zfile->next = z->files; + z->files = zfile; + + pat += 0x2e + fn_size + get_word (buf + 30) + get_word (buf + 32); + nr++; + } + return ZIP_OK; +} + +zip * +zip_open (const char *fname, int *err) +{ + unsigned char buf[22]; + zip *z; + FILE *f; + + f = fopen (fname, "rb"); + if (NULL == f) { + *err = ZIP_EOPEN; + return NULL; + } + + z = malloc (sizeof (zip)); + memset (z, 0, sizeof (zip)); + z->f = f; + + if (find_cd (z)) { + zip_close (z); + *err = ZIP_NOSIG; + return NULL; + } + + fseek (f, z->cd_pos, SEEK_SET); + if (fread (buf, 22, 1, f) != 1) { + zip_close (z); + *err = ZIP_EREAD; + return NULL; + } + z->nr_files = get_word (buf + 10); + if (get_word (buf + 8) != z->nr_files) { + zip_close (z); + *err = ZIP_NOMULTI; + return NULL; + } + z->cd_size = get_long (buf + 12); + z->cd_offset = get_long (buf + 16); + z->rem_size = get_word (buf + 20); + z->head_size = z->cd_pos - (z->cd_offset + z->cd_size); + + *err = list_files (z); + if (*err != ZIP_OK) { + zip_close (z); + return NULL; + } + + *err = ZIP_OK; + return z; +} + +void +zip_close (zip *z) +{ + struct zipfile *zfile, *tmp; + + zfile = z->files; + while (zfile) { + tmp = zfile->next; + if (zfile->name) free (zfile->name); + free (zfile); + zfile = tmp; + } + z->files = NULL; + if (z->f) fclose (z->f); + z->f = NULL; +} + +static struct zipfile * +find_file (zip *z, const char *name) +{ + struct zipfile *zfile; + + zfile = z->files; + while (zfile) { + if (strcmp (zfile->name, name) == 0) return zfile; + zfile = zfile->next; + } + return NULL; +} + +static int +seek_file (zip *z, struct zipfile *zfile) +{ + unsigned char buf[30]; + + fseek (z->f, zfile->pos + z->head_size, SEEK_SET); + if (fread (buf, 30, 1, z->f) != 1) return ZIP_EREAD; + if (get_long (buf) != 0x04034b50) return ZIP_BADZIP; + fseek (z->f, get_word (buf + 26) + get_word (buf + 28), SEEK_CUR); + return ZIP_OK; +} + +iks * +zip_load_xml (zip *z, const char *name, int *err) +{ + iksparser *prs; + char *real_buf; + iks *x; + struct zipfile *zfile; + + *err = ZIP_OK; + + zfile = find_file (z, name); + if (!zfile) { + *err = ZIP_NOFILE; + return NULL; + } + + seek_file (z, zfile); + + real_buf = malloc (zfile->real_size + 1); + if (zfile->zip_size < zfile->real_size) { + char *zip_buf; + z_stream zs; + zs.zalloc = NULL; + zs.zfree = NULL; + zs.opaque = NULL; + zip_buf = malloc (zfile->zip_size); + fread (zip_buf, zfile->zip_size, 1, z->f); + zs.next_in = zip_buf; + zs.avail_in = zfile->zip_size; + zs.next_out = real_buf; + zs.avail_out = zfile->real_size; + inflateInit2 (&zs, -MAX_WBITS); + inflate (&zs, Z_FINISH); + inflateEnd (&zs); + free (zip_buf); + } else { + fread (real_buf, zfile->real_size, 1, z->f); + } + + real_buf[zfile->real_size] = '\0'; + prs = iks_dom_new (&x); + iks_parse (prs, real_buf, zfile->real_size, 1); + iks_parser_delete (prs); + free (real_buf); + return x; +} + +unsigned long zip_get_size (zip *z, const char *name) +{ + struct zipfile *zf; + + zf = find_file (z, name); + if (!zf) return 0; + return zf->real_size; +} + +int zip_load (zip *z, const char *name, char *buf) +{ + struct zipfile *zfile; + + zfile = find_file (z, name); + if (!zfile) return ZIP_NOFILE; + + seek_file (z, zfile); + + if (zfile->zip_size < zfile->real_size) { + char *zip_buf; + z_stream zs; + zs.zalloc = NULL; + zs.zfree = NULL; + zs.opaque = NULL; + zip_buf = malloc (zfile->zip_size); + fread (zip_buf, zfile->zip_size, 1, z->f); + zs.next_in = zip_buf; + zs.avail_in = zfile->zip_size; + zs.next_out = buf; + zs.avail_out = zfile->real_size; + inflateInit2 (&zs, -MAX_WBITS); + inflate (&zs, Z_FINISH); + inflateEnd (&zs); + free (zip_buf); + } else { + fread (buf, zfile->real_size, 1, z->f); + } + + return ZIP_OK; +} diff --git a/backend/impress/zip.h b/backend/impress/zip.h new file mode 100644 index 00000000..23ff3631 --- /dev/null +++ b/backend/impress/zip.h @@ -0,0 +1,18 @@ +/* imposter (OO.org Impress viewer) +** Copyright (C) 2003-2005 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU General Public License. +*/ + +struct zip_struct; +typedef struct zip_struct zip; + +char *zip_error (int err); + +zip *zip_open (const char *fname, int *err); +void zip_close (zip *z); + +iks *zip_load_xml (zip *z, const char *name, int *err); + +unsigned long zip_get_size (zip *z, const char *name); +int zip_load (zip *z, const char *name, char *buf); diff --git a/backend/pdf/Makefile.am b/backend/pdf/Makefile.am new file mode 100644 index 00000000..725a512c --- /dev/null +++ b/backend/pdf/Makefile.am @@ -0,0 +1,34 @@ +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/libdocument \ + -DMATELOCALEDIR=\"$(datadir)/locale\" \ + -DEVINCE_COMPILATION \ + $(BACKEND_CFLAGS) \ + $(POPPLER_CFLAGS) \ + $(WARN_CXXFLAGS) \ + $(DISABLE_DEPRECATED) + +backend_LTLIBRARIES = libpdfdocument.la + +libpdfdocument_la_SOURCES = \ + ev-poppler.cc \ + ev-poppler.h + +libpdfdocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS) +libpdfdocument_la_LIBADD = \ + $(top_builddir)/libdocument/libevdocument.la \ + $(BACKEND_LIBS) \ + $(POPPLER_LIBS) \ + $(CAIRO_PDF_LIBS) \ + $(CAIRO_PS_LIBS) + +backend_in_files = pdfdocument.evince-backend.in +backend_DATA = $(backend_in_files:.evince-backend.in=.evince-backend) + +EXTRA_DIST = $(backend_in_files) + +CLEANFILES = $(backend_DATA) + +@EV_INTLTOOL_EVINCE_BACKEND_RULE@ + +-include $(top_srcdir)/git.mk diff --git a/backend/pdf/ev-poppler.cc b/backend/pdf/ev-poppler.cc new file mode 100644 index 00000000..e2abb55d --- /dev/null +++ b/backend/pdf/ev-poppler.cc @@ -0,0 +1,3290 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* this file is part of evince, a mate document viewer + * + * Copyright (C) 2009, Juanjo MarĆ­n + * Copyright (C) 2004, Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_CAIRO_PDF +#include +#endif +#ifdef HAVE_CAIRO_PS +#include +#endif +#include + +#include "ev-poppler.h" +#include "ev-file-exporter.h" +#include "ev-document-find.h" +#include "ev-document-misc.h" +#include "ev-document-links.h" +#include "ev-document-images.h" +#include "ev-document-fonts.h" +#include "ev-document-security.h" +#include "ev-document-thumbnails.h" +#include "ev-document-transition.h" +#include "ev-document-forms.h" +#include "ev-document-layers.h" +#include "ev-document-print.h" +#include "ev-document-annotations.h" +#include "ev-document-attachments.h" +#include "ev-document-text.h" +#include "ev-selection.h" +#include "ev-transition-effect.h" +#include "ev-attachment.h" +#include "ev-image.h" + +#include +#include +#include +#include + +#if (defined (HAVE_CAIRO_PDF) || defined (HAVE_CAIRO_PS)) +#define HAVE_CAIRO_PRINT +#endif + +/* fields from the XMP Rights Management Schema, XMP Specification Sept 2005, pag. 45 */ +#define LICENSE_MARKED "/x:xmpmeta/rdf:RDF/rdf:Description/xmpRights:Marked" +#define LICENSE_TEXT "/x:xmpmeta/rdf:RDF/rdf:Description/dc:rights/rdf:Alt/rdf:li[lang('%s')]" +#define LICENSE_WEB_STATEMENT "/x:xmpmeta/rdf:RDF/rdf:Description/xmpRights:WebStatement" +/* license field from Creative Commons schema, http://creativecommons.org/ns */ +#define LICENSE_URI "/x:xmpmeta/rdf:RDF/rdf:Description/cc:license/@rdf:resource" + +typedef struct { + EvFileExporterFormat format; + + /* Pages per sheet */ + gint pages_per_sheet; + gint pages_printed; + gint pages_x; + gint pages_y; + gdouble paper_width; + gdouble paper_height; + +#ifdef HAVE_CAIRO_PRINT + cairo_t *cr; +#else + PopplerPSFile *ps_file; +#endif +} PdfPrintContext; + +struct _PdfDocumentClass +{ + EvDocumentClass parent_class; +}; + +struct _PdfDocument +{ + EvDocument parent_instance; + + PopplerDocument *document; + gchar *password; + gboolean forms_modified; + gboolean annots_modified; + + PopplerFontInfo *font_info; + PopplerFontsIter *fonts_iter; + int fonts_scanned_pages; + + PdfPrintContext *print_ctx; + + GList *layers; + GHashTable *annots; +}; + +static void pdf_document_security_iface_init (EvDocumentSecurityInterface *iface); +static void pdf_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); +static void pdf_document_document_links_iface_init (EvDocumentLinksInterface *iface); +static void pdf_document_document_images_iface_init (EvDocumentImagesInterface *iface); +static void pdf_document_document_forms_iface_init (EvDocumentFormsInterface *iface); +static void pdf_document_document_fonts_iface_init (EvDocumentFontsInterface *iface); +static void pdf_document_document_layers_iface_init (EvDocumentLayersInterface *iface); +static void pdf_document_document_print_iface_init (EvDocumentPrintInterface *iface); +static void pdf_document_document_annotations_iface_init (EvDocumentAnnotationsInterface *iface); +static void pdf_document_document_attachments_iface_init (EvDocumentAttachmentsInterface *iface); +static void pdf_document_find_iface_init (EvDocumentFindInterface *iface); +static void pdf_document_file_exporter_iface_init (EvFileExporterInterface *iface); +static void pdf_selection_iface_init (EvSelectionInterface *iface); +static void pdf_document_page_transition_iface_init (EvDocumentTransitionInterface *iface); +static void pdf_document_text_iface_init (EvDocumentTextInterface *iface); +static void pdf_document_thumbnails_get_dimensions (EvDocumentThumbnails *document_thumbnails, + EvRenderContext *rc, + gint *width, + gint *height); +static int pdf_document_get_n_pages (EvDocument *document); + +static EvLinkDest *ev_link_dest_from_dest (PdfDocument *pdf_document, + PopplerDest *dest); +static EvLink *ev_link_from_action (PdfDocument *pdf_document, + PopplerAction *action); +static void pdf_print_context_free (PdfPrintContext *ctx); +static gboolean attachment_save_to_buffer (PopplerAttachment *attachment, + gchar **buffer, + gsize *buffer_size, + GError **error); + +EV_BACKEND_REGISTER_WITH_CODE (PdfDocument, pdf_document, + { + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_SECURITY, + pdf_document_security_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, + pdf_document_document_thumbnails_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_LINKS, + pdf_document_document_links_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_IMAGES, + pdf_document_document_images_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_FORMS, + pdf_document_document_forms_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_FONTS, + pdf_document_document_fonts_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_LAYERS, + pdf_document_document_layers_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_PRINT, + pdf_document_document_print_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_ANNOTATIONS, + pdf_document_document_annotations_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_ATTACHMENTS, + pdf_document_document_attachments_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_FIND, + pdf_document_find_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_FILE_EXPORTER, + pdf_document_file_exporter_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_SELECTION, + pdf_selection_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_TRANSITION, + pdf_document_page_transition_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_TEXT, + pdf_document_text_iface_init); + }); + +static void +pdf_document_dispose (GObject *object) +{ + PdfDocument *pdf_document = PDF_DOCUMENT(object); + + if (pdf_document->print_ctx) { + pdf_print_context_free (pdf_document->print_ctx); + pdf_document->print_ctx = NULL; + } + + if (pdf_document->annots) { + g_hash_table_destroy (pdf_document->annots); + pdf_document->annots = NULL; + } + + if (pdf_document->document) { + g_object_unref (pdf_document->document); + } + + if (pdf_document->font_info) { + poppler_font_info_free (pdf_document->font_info); + } + + if (pdf_document->fonts_iter) { + poppler_fonts_iter_free (pdf_document->fonts_iter); + } + + if (pdf_document->layers) { + g_list_foreach (pdf_document->layers, (GFunc)g_object_unref, NULL); + g_list_free (pdf_document->layers); + } + + G_OBJECT_CLASS (pdf_document_parent_class)->dispose (object); +} + +static void +pdf_document_init (PdfDocument *pdf_document) +{ + pdf_document->password = NULL; +} + +static void +convert_error (GError *poppler_error, + GError **error) +{ + if (poppler_error == NULL) + return; + + if (poppler_error->domain == POPPLER_ERROR) { + /* convert poppler errors into EvDocument errors */ + gint code = EV_DOCUMENT_ERROR_INVALID; + if (poppler_error->code == POPPLER_ERROR_INVALID) + code = EV_DOCUMENT_ERROR_INVALID; + else if (poppler_error->code == POPPLER_ERROR_ENCRYPTED) + code = EV_DOCUMENT_ERROR_ENCRYPTED; + + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + code, + poppler_error->message); + + g_error_free (poppler_error); + } else { + g_propagate_error (error, poppler_error); + } +} + + +/* EvDocument */ +static gboolean +pdf_document_save (EvDocument *document, + const char *uri, + GError **error) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document); + gboolean retval; + GError *poppler_error = NULL; + + if (pdf_document->forms_modified || pdf_document->annots_modified) { + retval = poppler_document_save (pdf_document->document, + uri, &poppler_error); + if (retval) { + pdf_document->forms_modified = FALSE; + pdf_document->annots_modified = FALSE; + } + } else { + retval = poppler_document_save_a_copy (pdf_document->document, + uri, &poppler_error); + } + + if (! retval) + convert_error (poppler_error, error); + + return retval; +} + +static gboolean +pdf_document_load (EvDocument *document, + const char *uri, + GError **error) +{ + GError *poppler_error = NULL; + PdfDocument *pdf_document = PDF_DOCUMENT (document); + + pdf_document->document = + poppler_document_new_from_file (uri, pdf_document->password, &poppler_error); + + if (pdf_document->document == NULL) { + convert_error (poppler_error, error); + return FALSE; + } + + return TRUE; +} + +static int +pdf_document_get_n_pages (EvDocument *document) +{ + return poppler_document_get_n_pages (PDF_DOCUMENT (document)->document); +} + +static EvPage * +pdf_document_get_page (EvDocument *document, + gint index) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document); + PopplerPage *poppler_page; + EvPage *page; + + poppler_page = poppler_document_get_page (pdf_document->document, index); + page = ev_page_new (index); + page->backend_page = (EvBackendPage)g_object_ref (poppler_page); + page->backend_destroy_func = (EvBackendPageDestroyFunc)g_object_unref; + g_object_unref (poppler_page); + + return page; +} + +static void +pdf_document_get_page_size (EvDocument *document, + EvPage *page, + double *width, + double *height) +{ + g_return_if_fail (POPPLER_IS_PAGE (page->backend_page)); + + poppler_page_get_size (POPPLER_PAGE (page->backend_page), width, height); +} + +static char * +pdf_document_get_page_label (EvDocument *document, + EvPage *page) +{ + char *label = NULL; + + g_return_val_if_fail (POPPLER_IS_PAGE (page->backend_page), NULL); + + g_object_get (G_OBJECT (page->backend_page), + "label", &label, + NULL); + return label; +} + +static cairo_surface_t * +pdf_page_render (PopplerPage *page, + gint width, + gint height, + EvRenderContext *rc) +{ + cairo_surface_t *surface; + cairo_t *cr; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + width, height); + cr = cairo_create (surface); + + switch (rc->rotation) { + case 90: + cairo_translate (cr, width, 0); + break; + case 180: + cairo_translate (cr, width, height); + break; + case 270: + cairo_translate (cr, 0, height); + break; + default: + cairo_translate (cr, 0, 0); + } + cairo_scale (cr, rc->scale, rc->scale); + cairo_rotate (cr, rc->rotation * G_PI / 180.0); + poppler_page_render (page, cr); + + cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OVER); + cairo_set_source_rgb (cr, 1., 1., 1.); + cairo_paint (cr); + + cairo_destroy (cr); + + return surface; +} + +static cairo_surface_t * +pdf_document_render (EvDocument *document, + EvRenderContext *rc) +{ + PdfDocument *pdf_document; + PopplerPage *poppler_page; + double width_points, height_points; + gint width, height; + + poppler_page = POPPLER_PAGE (rc->page->backend_page); + + poppler_page_get_size (poppler_page, + &width_points, &height_points); + + if (rc->rotation == 90 || rc->rotation == 270) { + width = (int) ((height_points * rc->scale) + 0.5); + height = (int) ((width_points * rc->scale) + 0.5); + } else { + width = (int) ((width_points * rc->scale) + 0.5); + height = (int) ((height_points * rc->scale) + 0.5); + } + + return pdf_page_render (poppler_page, + width, height, rc); +} + +/* reference: +http://www.pdfa.org/lib/exe/fetch.php?id=pdfa%3Aen%3Atechdoc&cache=cache&media=pdfa:techdoc:tn0001_pdfa-1_and_namespaces_2008-03-18.pdf */ +static char * +pdf_document_get_format_from_metadata (xmlDocPtr doc, + xmlXPathContextPtr xpathCtx) +{ + xmlXPathObjectPtr xpathObj; + xmlChar *part = NULL; + xmlChar *conf = NULL; + char *result = NULL; + int i; + + /* add pdf/a namespaces */ + xmlXPathRegisterNs (xpathCtx, BAD_CAST "x", BAD_CAST "adobe:ns:meta/"); + xmlXPathRegisterNs (xpathCtx, BAD_CAST "rdf", BAD_CAST "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + xmlXPathRegisterNs (xpathCtx, BAD_CAST "pdfaid", BAD_CAST "http://www.aiim.org/pdfa/ns/id/"); + + /* reads pdf/a part */ + /* first syntax: child node */ + xpathObj = xmlXPathEvalExpression (BAD_CAST "/x:xmpmeta/rdf:RDF/rdf:Description/pdfaid:part", xpathCtx); + if (xpathObj != NULL) { + if (xpathObj->nodesetval != NULL && xpathObj->nodesetval->nodeNr != 0) + part = xmlNodeGetContent (xpathObj->nodesetval->nodeTab[0]); + + xmlXPathFreeObject (xpathObj); + } + if (part == NULL) { + /* second syntax: attribute */ + xpathObj = xmlXPathEvalExpression (BAD_CAST "/x:xmpmeta/rdf:RDF/rdf:Description/@pdfaid:part", xpathCtx); + if (xpathObj != NULL) { + if (xpathObj->nodesetval != NULL && xpathObj->nodesetval->nodeNr != 0) + part = xmlNodeGetContent (xpathObj->nodesetval->nodeTab[0]); + + xmlXPathFreeObject (xpathObj); + } + } + + /* reads pdf/a conformance */ + /* first syntax: child node */ + xpathObj = xmlXPathEvalExpression (BAD_CAST "/x:xmpmeta/rdf:RDF/rdf:Description/pdfaid:conformance", xpathCtx); + if (xpathObj != NULL) { + if (xpathObj->nodesetval != NULL && xpathObj->nodesetval->nodeNr != 0) + conf = xmlNodeGetContent (xpathObj->nodesetval->nodeTab[0]); + + xmlXPathFreeObject (xpathObj); + } + if (conf == NULL) { + /* second syntax: attribute */ + xpathObj = xmlXPathEvalExpression (BAD_CAST "/x:xmpmeta/rdf:RDF/rdf:Description/@pdfaid:conformance", xpathCtx); + if (xpathObj != NULL) { + if (xpathObj->nodesetval != NULL && xpathObj->nodesetval->nodeNr != 0) + conf = xmlNodeGetContent (xpathObj->nodesetval->nodeTab[0]); + + xmlXPathFreeObject (xpathObj); + } + } + + if (part != NULL && conf != NULL) { + /* makes conf lowercase */ + for (i = 0; conf[i]; i++) + conf[i] = g_ascii_tolower (conf[i]); + + /* return buffer */ + result = g_strdup_printf ("PDF/A - %s%s", part, conf); + } + + /* Cleanup */ + xmlFree (part); + xmlFree (conf); + + return result; +} + +static EvDocumentLicense * +pdf_document_get_license_from_metadata (xmlDocPtr doc, + xmlXPathContextPtr xpathCtx) +{ + xmlXPathObjectPtr xpathObj; + xmlChar *marked = NULL; + const char *language_string; + char *aux; + gchar **tags; + gchar *tag, *tag_aux; + int i, j; + EvDocumentLicense *license; + + /* register namespaces */ + xmlXPathRegisterNs (xpathCtx, BAD_CAST "x", BAD_CAST "adobe:ns:meta/"); + xmlXPathRegisterNs (xpathCtx, BAD_CAST "rdf", BAD_CAST "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + xmlXPathRegisterNs (xpathCtx, BAD_CAST "dc", BAD_CAST "http://purl.org/dc/elements/1.1/"); + /* XMP Rights Management Schema */ + xmlXPathRegisterNs (xpathCtx, BAD_CAST "xmpRights", BAD_CAST "http://ns.adobe.com/xap/1.0/rights/"); + /* Creative Commons Schema */ + xmlXPathRegisterNs (xpathCtx, BAD_CAST "cc", BAD_CAST "http://creativecommons.org/ns#"); + + /* checking if the document has been marked as defined on the XMP Rights + * Management Schema */ + xpathObj = xmlXPathEvalExpression (BAD_CAST LICENSE_MARKED, xpathCtx); + if (xpathObj != NULL) { + if (xpathObj->nodesetval != NULL && + xpathObj->nodesetval->nodeNr != 0) + marked = xmlNodeGetContent (xpathObj->nodesetval->nodeTab[0]); + xmlXPathFreeObject (xpathObj); + } + + /* a) Not marked => No XMP Rights information */ + if (!marked) { + xmlFree (marked); + return NULL; + } + + license = ev_document_license_new (); + + /* b) Marked False => Public Domain, no copyrighted material and no + * license needed */ + if (g_strrstr ((char *) marked, "False") != NULL) { + license->text = g_strdup (_("This work is in the Public Domain")); + /* c) Marked True => Copyrighted material */ + } else { + /* Checking usage terms as defined by the XMP Rights Management + * Schema. This field is recomended to be checked by Creative + * Commons */ + /* 1) checking for a suitable localized string */ + language_string = pango_language_to_string (gtk_get_default_language ()); + tags = g_strsplit (language_string, "-", -1); + i = g_strv_length (tags); + while (i-- && !license->text) { + tag = g_strdup (tags[0]); + for (j = 1; j <= i; j++) { + tag_aux = g_strdup_printf ("%s-%s", tag, tags[j]); + g_free (tag); + tag = tag_aux; + } + aux = g_strdup_printf (LICENSE_TEXT, tag); + xpathObj = xmlXPathEvalExpression (BAD_CAST aux, xpathCtx); + if (xpathObj != NULL) { + if (xpathObj->nodesetval != NULL && + xpathObj->nodesetval->nodeNr != 0) + license->text = (gchar *)xmlNodeGetContent (xpathObj->nodesetval->nodeTab[0]); + xmlXPathFreeObject (xpathObj); + } + g_free (tag); + g_free (aux); + } + g_strfreev(tags); + + /* 2) if not, use the default string */ + if (!license->text) { + aux = g_strdup_printf (LICENSE_TEXT, "x-default"); + xpathObj = xmlXPathEvalExpression (BAD_CAST aux, xpathCtx); + if (xpathObj != NULL) { + if (xpathObj->nodesetval != NULL && + xpathObj->nodesetval->nodeNr != 0) + license->text = (gchar *)xmlNodeGetContent (xpathObj->nodesetval->nodeTab[0]); + xmlXPathFreeObject (xpathObj); + } + g_free (aux); + } + + /* Checking the license URI as defined by the Creative Commons + * Schema. This field is recomended to be checked by Creative + * Commons */ + xpathObj = xmlXPathEvalExpression (BAD_CAST LICENSE_URI, xpathCtx); + if (xpathObj != NULL) { + if (xpathObj->nodesetval != NULL && + xpathObj->nodesetval->nodeNr != 0) + license->uri = (gchar *)xmlNodeGetContent (xpathObj->nodesetval->nodeTab[0]); + xmlXPathFreeObject (xpathObj); + } + + /* Checking the web statement as defined by the XMP Rights + * Management Schema. Checking it out is a sort of above-and-beyond + * the basic recommendations by Creative Commons. It can be + * considered as a "reinforcement" approach to add certainty. */ + xpathObj = xmlXPathEvalExpression (BAD_CAST LICENSE_WEB_STATEMENT, xpathCtx); + if (xpathObj != NULL) { + if (xpathObj->nodesetval != NULL && + xpathObj->nodesetval->nodeNr != 0) + license->web_statement = (gchar *)xmlNodeGetContent (xpathObj->nodesetval->nodeTab[0]); + xmlXPathFreeObject (xpathObj); + } + } + xmlFree (marked); + + if (!license->text && !license->uri && !license->web_statement) { + ev_document_license_free (license); + return NULL; + } + + return license; +} + +static void +pdf_document_parse_metadata (const gchar *metadata, + EvDocumentInfo *info) +{ + xmlDocPtr doc; + xmlXPathContextPtr xpathCtx; + gchar *fmt; + + doc = xmlParseMemory (metadata, strlen (metadata)); + if (doc == NULL) + return; /* invalid xml metadata */ + + xpathCtx = xmlXPathNewContext (doc); + if (xpathCtx == NULL) { + xmlFreeDoc (doc); + return; /* invalid xpath context */ + } + + fmt = pdf_document_get_format_from_metadata (doc, xpathCtx); + if (fmt != NULL) { + g_free (info->format); + info->format = fmt; + } + + info->license = pdf_document_get_license_from_metadata (doc, xpathCtx); + + xmlXPathFreeContext (xpathCtx); + xmlFreeDoc (doc); +} + + +static EvDocumentInfo * +pdf_document_get_info (EvDocument *document) +{ + EvDocumentInfo *info; + PopplerPageLayout layout; + PopplerPageMode mode; + PopplerViewerPreferences view_prefs; + PopplerPermissions permissions; + EvPage *page; + char *metadata; + + info = g_new0 (EvDocumentInfo, 1); + + info->fields_mask = EV_DOCUMENT_INFO_TITLE | + EV_DOCUMENT_INFO_FORMAT | + EV_DOCUMENT_INFO_AUTHOR | + EV_DOCUMENT_INFO_SUBJECT | + EV_DOCUMENT_INFO_KEYWORDS | + EV_DOCUMENT_INFO_LAYOUT | + EV_DOCUMENT_INFO_START_MODE | + EV_DOCUMENT_INFO_PERMISSIONS | + EV_DOCUMENT_INFO_UI_HINTS | + EV_DOCUMENT_INFO_CREATOR | + EV_DOCUMENT_INFO_PRODUCER | + EV_DOCUMENT_INFO_CREATION_DATE | + EV_DOCUMENT_INFO_MOD_DATE | + EV_DOCUMENT_INFO_LINEARIZED | + EV_DOCUMENT_INFO_N_PAGES | + EV_DOCUMENT_INFO_SECURITY | + EV_DOCUMENT_INFO_PAPER_SIZE | + EV_DOCUMENT_INFO_LICENSE; + + g_object_get (PDF_DOCUMENT (document)->document, + "title", &(info->title), + "format", &(info->format), + "author", &(info->author), + "subject", &(info->subject), + "keywords", &(info->keywords), + "page-mode", &mode, + "page-layout", &layout, + "viewer-preferences", &view_prefs, + "permissions", &permissions, + "creator", &(info->creator), + "producer", &(info->producer), + "creation-date", &(info->creation_date), + "mod-date", &(info->modified_date), + "linearized", &(info->linearized), + "metadata", &metadata, + NULL); + + if (metadata != NULL) { + pdf_document_parse_metadata (metadata, info); + g_free (metadata); + } + + info->n_pages = ev_document_get_n_pages (document); + + if (info->n_pages > 0) { + ev_document_get_page_size (document, 0, + &(info->paper_width), + &(info->paper_height)); + // Convert to mm. + info->paper_width = info->paper_width / 72.0f * 25.4f; + info->paper_height = info->paper_height / 72.0f * 25.4f; + } + + switch (layout) { + case POPPLER_PAGE_LAYOUT_SINGLE_PAGE: + info->layout = EV_DOCUMENT_LAYOUT_SINGLE_PAGE; + break; + case POPPLER_PAGE_LAYOUT_ONE_COLUMN: + info->layout = EV_DOCUMENT_LAYOUT_ONE_COLUMN; + break; + case POPPLER_PAGE_LAYOUT_TWO_COLUMN_LEFT: + info->layout = EV_DOCUMENT_LAYOUT_TWO_COLUMN_LEFT; + break; + case POPPLER_PAGE_LAYOUT_TWO_COLUMN_RIGHT: + info->layout = EV_DOCUMENT_LAYOUT_TWO_COLUMN_RIGHT; + case POPPLER_PAGE_LAYOUT_TWO_PAGE_LEFT: + info->layout = EV_DOCUMENT_LAYOUT_TWO_PAGE_LEFT; + break; + case POPPLER_PAGE_LAYOUT_TWO_PAGE_RIGHT: + info->layout = EV_DOCUMENT_LAYOUT_TWO_PAGE_RIGHT; + break; + default: + break; + } + + switch (mode) { + case POPPLER_PAGE_MODE_NONE: + info->mode = EV_DOCUMENT_MODE_NONE; + break; + case POPPLER_PAGE_MODE_USE_THUMBS: + info->mode = EV_DOCUMENT_MODE_USE_THUMBS; + break; + case POPPLER_PAGE_MODE_USE_OC: + info->mode = EV_DOCUMENT_MODE_USE_OC; + break; + case POPPLER_PAGE_MODE_FULL_SCREEN: + info->mode = EV_DOCUMENT_MODE_FULL_SCREEN; + break; + case POPPLER_PAGE_MODE_USE_ATTACHMENTS: + info->mode = EV_DOCUMENT_MODE_USE_ATTACHMENTS; + default: + break; + } + + info->ui_hints = 0; + if (view_prefs & POPPLER_VIEWER_PREFERENCES_HIDE_TOOLBAR) { + info->ui_hints |= EV_DOCUMENT_UI_HINT_HIDE_TOOLBAR; + } + if (view_prefs & POPPLER_VIEWER_PREFERENCES_HIDE_MENUBAR) { + info->ui_hints |= EV_DOCUMENT_UI_HINT_HIDE_MENUBAR; + } + if (view_prefs & POPPLER_VIEWER_PREFERENCES_HIDE_WINDOWUI) { + info->ui_hints |= EV_DOCUMENT_UI_HINT_HIDE_WINDOWUI; + } + if (view_prefs & POPPLER_VIEWER_PREFERENCES_FIT_WINDOW) { + info->ui_hints |= EV_DOCUMENT_UI_HINT_FIT_WINDOW; + } + if (view_prefs & POPPLER_VIEWER_PREFERENCES_CENTER_WINDOW) { + info->ui_hints |= EV_DOCUMENT_UI_HINT_CENTER_WINDOW; + } + if (view_prefs & POPPLER_VIEWER_PREFERENCES_DISPLAY_DOC_TITLE) { + info->ui_hints |= EV_DOCUMENT_UI_HINT_DISPLAY_DOC_TITLE; + } + if (view_prefs & POPPLER_VIEWER_PREFERENCES_DIRECTION_RTL) { + info->ui_hints |= EV_DOCUMENT_UI_HINT_DIRECTION_RTL; + } + + info->permissions = 0; + if (permissions & POPPLER_PERMISSIONS_OK_TO_PRINT) { + info->permissions |= EV_DOCUMENT_PERMISSIONS_OK_TO_PRINT; + } + if (permissions & POPPLER_PERMISSIONS_OK_TO_MODIFY) { + info->permissions |= EV_DOCUMENT_PERMISSIONS_OK_TO_MODIFY; + } + if (permissions & POPPLER_PERMISSIONS_OK_TO_COPY) { + info->permissions |= EV_DOCUMENT_PERMISSIONS_OK_TO_COPY; + } + if (permissions & POPPLER_PERMISSIONS_OK_TO_ADD_NOTES) { + info->permissions |= EV_DOCUMENT_PERMISSIONS_OK_TO_ADD_NOTES; + } + + if (ev_document_security_has_document_security (EV_DOCUMENT_SECURITY (document))) { + /* translators: this is the document security state */ + info->security = g_strdup (_("Yes")); + } else { + /* translators: this is the document security state */ + info->security = g_strdup (_("No")); + } + + return info; +} + +static gboolean +pdf_document_get_backend_info (EvDocument *document, EvDocumentBackendInfo *info) +{ + PopplerBackend backend; + + backend = poppler_get_backend (); + switch (backend) { + case POPPLER_BACKEND_CAIRO: + info->name = "poppler/cairo"; + break; + case POPPLER_BACKEND_SPLASH: + info->name = "poppler/splash"; + break; + default: + info->name = "poppler/unknown"; + break; + } + + info->version = poppler_get_version (); + + return TRUE; +} + +static gboolean +pdf_document_support_synctex (EvDocument *document) +{ + return TRUE; +} + +static void +pdf_document_class_init (PdfDocumentClass *klass) +{ + GObjectClass *g_object_class = G_OBJECT_CLASS (klass); + EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass); + + g_object_class->dispose = pdf_document_dispose; + + ev_document_class->save = pdf_document_save; + ev_document_class->load = pdf_document_load; + ev_document_class->get_n_pages = pdf_document_get_n_pages; + ev_document_class->get_page = pdf_document_get_page; + ev_document_class->get_page_size = pdf_document_get_page_size; + ev_document_class->get_page_label = pdf_document_get_page_label; + ev_document_class->render = pdf_document_render; + ev_document_class->get_info = pdf_document_get_info; + ev_document_class->get_backend_info = pdf_document_get_backend_info; + ev_document_class->support_synctex = pdf_document_support_synctex; +} + +/* EvDocumentSecurity */ +static gboolean +pdf_document_has_document_security (EvDocumentSecurity *document_security) +{ + /* FIXME: do we really need to have this? */ + return FALSE; +} + +static void +pdf_document_set_password (EvDocumentSecurity *document_security, + const char *password) +{ + PdfDocument *document = PDF_DOCUMENT (document_security); + + if (document->password) + g_free (document->password); + + document->password = g_strdup (password); +} + +static void +pdf_document_security_iface_init (EvDocumentSecurityInterface *iface) +{ + iface->has_document_security = pdf_document_has_document_security; + iface->set_password = pdf_document_set_password; +} + +static gdouble +pdf_document_fonts_get_progress (EvDocumentFonts *document_fonts) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document_fonts); + int n_pages; + + n_pages = pdf_document_get_n_pages (EV_DOCUMENT (pdf_document)); + + return (double)pdf_document->fonts_scanned_pages / (double)n_pages; +} + +static gboolean +pdf_document_fonts_scan (EvDocumentFonts *document_fonts, + int n_pages) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document_fonts); + gboolean result; + + g_return_val_if_fail (PDF_IS_DOCUMENT (document_fonts), FALSE); + + if (pdf_document->font_info == NULL) { + pdf_document->font_info = poppler_font_info_new (pdf_document->document); + } + + if (pdf_document->fonts_iter) { + poppler_fonts_iter_free (pdf_document->fonts_iter); + } + + pdf_document->fonts_scanned_pages += n_pages; + + result = poppler_font_info_scan (pdf_document->font_info, n_pages, + &pdf_document->fonts_iter); + if (!result) { + pdf_document->fonts_scanned_pages = 0; + poppler_font_info_free (pdf_document->font_info); + pdf_document->font_info = NULL; + } + + return result; +} + +static const char * +font_type_to_string (PopplerFontType type) +{ + switch (type) { + case POPPLER_FONT_TYPE_TYPE1: + return _("Type 1"); + case POPPLER_FONT_TYPE_TYPE1C: + return _("Type 1C"); + case POPPLER_FONT_TYPE_TYPE3: + return _("Type 3"); + case POPPLER_FONT_TYPE_TRUETYPE: + return _("TrueType"); + case POPPLER_FONT_TYPE_CID_TYPE0: + return _("Type 1 (CID)"); + case POPPLER_FONT_TYPE_CID_TYPE0C: + return _("Type 1C (CID)"); + case POPPLER_FONT_TYPE_CID_TYPE2: + return _("TrueType (CID)"); + default: + return _("Unknown font type"); + } +} + +static void +pdf_document_fonts_fill_model (EvDocumentFonts *document_fonts, + GtkTreeModel *model) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document_fonts); + PopplerFontsIter *iter = pdf_document->fonts_iter; + + g_return_if_fail (PDF_IS_DOCUMENT (document_fonts)); + + if (!iter) + return; + + do { + GtkTreeIter list_iter; + const char *name; + const char *type; + const char *embedded; + char *details; + + name = poppler_fonts_iter_get_name (iter); + + if (name == NULL) { + name = _("No name"); + } + + type = font_type_to_string ( + poppler_fonts_iter_get_font_type (iter)); + + if (poppler_fonts_iter_is_embedded (iter)) { + if (poppler_fonts_iter_is_subset (iter)) + embedded = _("Embedded subset"); + else + embedded = _("Embedded"); + } else { + embedded = _("Not embedded"); + } + + details = g_markup_printf_escaped ("%s\n%s", type, embedded); + + gtk_list_store_append (GTK_LIST_STORE (model), &list_iter); + gtk_list_store_set (GTK_LIST_STORE (model), &list_iter, + EV_DOCUMENT_FONTS_COLUMN_NAME, name, + EV_DOCUMENT_FONTS_COLUMN_DETAILS, details, + -1); + + g_free (details); + } while (poppler_fonts_iter_next (iter)); +} + +static void +pdf_document_document_fonts_iface_init (EvDocumentFontsInterface *iface) +{ + iface->fill_model = pdf_document_fonts_fill_model; + iface->scan = pdf_document_fonts_scan; + iface->get_progress = pdf_document_fonts_get_progress; +} + +static gboolean +pdf_document_links_has_document_links (EvDocumentLinks *document_links) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document_links); + PopplerIndexIter *iter; + + g_return_val_if_fail (PDF_IS_DOCUMENT (document_links), FALSE); + + iter = poppler_index_iter_new (pdf_document->document); + if (iter == NULL) + return FALSE; + poppler_index_iter_free (iter); + + return TRUE; +} + +static EvLinkDest * +ev_link_dest_from_dest (PdfDocument *pdf_document, + PopplerDest *dest) +{ + EvLinkDest *ev_dest = NULL; + const char *unimplemented_dest = NULL; + + g_assert (dest != NULL); + + switch (dest->type) { + case POPPLER_DEST_XYZ: { + PopplerPage *poppler_page; + double height; + + poppler_page = poppler_document_get_page (pdf_document->document, + MAX (0, dest->page_num - 1)); + poppler_page_get_size (poppler_page, NULL, &height); + ev_dest = ev_link_dest_new_xyz (dest->page_num - 1, + dest->left, + height - MIN (height, dest->top), + dest->zoom, + dest->change_left, + dest->change_top, + dest->change_zoom); + g_object_unref (poppler_page); + } + break; + case POPPLER_DEST_FITB: + case POPPLER_DEST_FIT: + ev_dest = ev_link_dest_new_fit (dest->page_num - 1); + break; + case POPPLER_DEST_FITBH: + case POPPLER_DEST_FITH: { + PopplerPage *poppler_page; + double height; + + poppler_page = poppler_document_get_page (pdf_document->document, + MAX (0, dest->page_num - 1)); + poppler_page_get_size (poppler_page, NULL, &height); + ev_dest = ev_link_dest_new_fith (dest->page_num - 1, + height - MIN (height, dest->top), + dest->change_top); + g_object_unref (poppler_page); + } + break; + case POPPLER_DEST_FITBV: + case POPPLER_DEST_FITV: + ev_dest = ev_link_dest_new_fitv (dest->page_num - 1, + dest->left, + dest->change_left); + break; + case POPPLER_DEST_FITR: { + PopplerPage *poppler_page; + double height; + + poppler_page = poppler_document_get_page (pdf_document->document, + MAX (0, dest->page_num - 1)); + poppler_page_get_size (poppler_page, NULL, &height); + ev_dest = ev_link_dest_new_fitr (dest->page_num - 1, + dest->left, + height - MIN (height, dest->bottom), + dest->right, + height - MIN (height, dest->top)); + g_object_unref (poppler_page); + } + break; + case POPPLER_DEST_NAMED: + ev_dest = ev_link_dest_new_named (dest->named_dest); + break; + case POPPLER_DEST_UNKNOWN: + unimplemented_dest = "POPPLER_DEST_UNKNOWN"; + break; + } + + if (unimplemented_dest) { + g_warning ("Unimplemented destination: %s, please post a " + "bug report in Evince bugzilla " + "(http://bugzilla.mate.org) with a testcase.", + unimplemented_dest); + } + + if (!ev_dest) + ev_dest = ev_link_dest_new_page (dest->page_num - 1); + + return ev_dest; +} + +static EvLink * +ev_link_from_action (PdfDocument *pdf_document, + PopplerAction *action) +{ + EvLink *link = NULL; + EvLinkAction *ev_action = NULL; + const char *unimplemented_action = NULL; + + switch (action->type) { + case POPPLER_ACTION_NONE: + break; + case POPPLER_ACTION_GOTO_DEST: { + EvLinkDest *dest; + + dest = ev_link_dest_from_dest (pdf_document, action->goto_dest.dest); + ev_action = ev_link_action_new_dest (dest); + } + break; + case POPPLER_ACTION_GOTO_REMOTE: { + EvLinkDest *dest; + + dest = ev_link_dest_from_dest (pdf_document, action->goto_remote.dest); + ev_action = ev_link_action_new_remote (dest, + action->goto_remote.file_name); + + } + break; + case POPPLER_ACTION_LAUNCH: + ev_action = ev_link_action_new_launch (action->launch.file_name, + action->launch.params); + break; + case POPPLER_ACTION_URI: + ev_action = ev_link_action_new_external_uri (action->uri.uri); + break; + case POPPLER_ACTION_NAMED: + ev_action = ev_link_action_new_named (action->named.named_dest); + break; + case POPPLER_ACTION_MOVIE: + unimplemented_action = "POPPLER_ACTION_MOVIE"; + break; +#if POPPLER_CHECK_VERSION (0, 13, 2) + case POPPLER_ACTION_RENDITION: + unimplemented_action = "POPPLER_ACTION_RENDITION"; + break; + case POPPLER_ACTION_OCG_STATE: + unimplemented_action = "POPPLER_ACTION_OCG_STATE"; + break; +#endif + case POPPLER_ACTION_UNKNOWN: + unimplemented_action = "POPPLER_ACTION_UNKNOWN"; + } + + if (unimplemented_action) { + g_warning ("Unimplemented action: %s, please post a bug report " + "in Evince bugzilla (http://bugzilla.mate.org) " + "with a testcase.", unimplemented_action); + } + + link = ev_link_new (action->any.title, ev_action); + + return link; +} + +static void +build_tree (PdfDocument *pdf_document, + GtkTreeModel *model, + GtkTreeIter *parent, + PopplerIndexIter *iter) +{ + + do { + GtkTreeIter tree_iter; + PopplerIndexIter *child; + PopplerAction *action; + EvLink *link = NULL; + gboolean expand; + char *title_markup; + + action = poppler_index_iter_get_action (iter); + expand = poppler_index_iter_is_open (iter); + + if (!action) + continue; + + switch (action->type) { + case POPPLER_ACTION_GOTO_DEST: { + /* For bookmarks, solve named destinations */ + if (action->goto_dest.dest->type == POPPLER_DEST_NAMED) { + PopplerDest *dest; + EvLinkDest *ev_dest = NULL; + EvLinkAction *ev_action; + + dest = poppler_document_find_dest (pdf_document->document, + action->goto_dest.dest->named_dest); + if (!dest) { + link = ev_link_from_action (pdf_document, action); + break; + } + + ev_dest = ev_link_dest_from_dest (pdf_document, dest); + poppler_dest_free (dest); + + ev_action = ev_link_action_new_dest (ev_dest); + link = ev_link_new (action->any.title, ev_action); + } else { + link = ev_link_from_action (pdf_document, action); + } + } + break; + default: + link = ev_link_from_action (pdf_document, action); + break; + } + + if (!link || strlen (ev_link_get_title (link)) <= 0) { + poppler_action_free (action); + if (link) + g_object_unref (link); + + continue; + } + + gtk_tree_store_append (GTK_TREE_STORE (model), &tree_iter, parent); + title_markup = g_markup_escape_text (ev_link_get_title (link), -1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &tree_iter, + EV_DOCUMENT_LINKS_COLUMN_MARKUP, title_markup, + EV_DOCUMENT_LINKS_COLUMN_LINK, link, + EV_DOCUMENT_LINKS_COLUMN_EXPAND, expand, + -1); + + g_free (title_markup); + g_object_unref (link); + + child = poppler_index_iter_get_child (iter); + if (child) + build_tree (pdf_document, model, &tree_iter, child); + poppler_index_iter_free (child); + poppler_action_free (action); + + } while (poppler_index_iter_next (iter)); +} + +static GtkTreeModel * +pdf_document_links_get_links_model (EvDocumentLinks *document_links) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document_links); + GtkTreeModel *model = NULL; + PopplerIndexIter *iter; + + g_return_val_if_fail (PDF_IS_DOCUMENT (document_links), NULL); + + iter = poppler_index_iter_new (pdf_document->document); + /* Create the model if we have items*/ + if (iter != NULL) { + model = (GtkTreeModel *) gtk_tree_store_new (EV_DOCUMENT_LINKS_COLUMN_NUM_COLUMNS, + G_TYPE_STRING, + G_TYPE_OBJECT, + G_TYPE_BOOLEAN, + G_TYPE_STRING); + build_tree (pdf_document, model, NULL, iter); + poppler_index_iter_free (iter); + } + + return model; +} + +static EvMappingList * +pdf_document_links_get_links (EvDocumentLinks *document_links, + EvPage *page) +{ + PdfDocument *pdf_document; + PopplerPage *poppler_page; + GList *retval = NULL; + GList *mapping_list; + GList *list; + double height; + + pdf_document = PDF_DOCUMENT (document_links); + poppler_page = POPPLER_PAGE (page->backend_page); + mapping_list = poppler_page_get_link_mapping (poppler_page); + poppler_page_get_size (poppler_page, NULL, &height); + + for (list = mapping_list; list; list = list->next) { + PopplerLinkMapping *link_mapping; + EvMapping *ev_link_mapping; + + link_mapping = (PopplerLinkMapping *)list->data; + ev_link_mapping = g_new (EvMapping, 1); + ev_link_mapping->data = ev_link_from_action (pdf_document, + link_mapping->action); + ev_link_mapping->area.x1 = link_mapping->area.x1; + ev_link_mapping->area.x2 = link_mapping->area.x2; + /* Invert this for X-style coordinates */ + ev_link_mapping->area.y1 = height - link_mapping->area.y2; + ev_link_mapping->area.y2 = height - link_mapping->area.y1; + + retval = g_list_prepend (retval, ev_link_mapping); + } + + poppler_page_free_link_mapping (mapping_list); + + return ev_mapping_list_new (page->index, g_list_reverse (retval), (GDestroyNotify)g_object_unref); +} + +static EvLinkDest * +pdf_document_links_find_link_dest (EvDocumentLinks *document_links, + const gchar *link_name) +{ + PdfDocument *pdf_document; + PopplerDest *dest; + EvLinkDest *ev_dest = NULL; + + pdf_document = PDF_DOCUMENT (document_links); + dest = poppler_document_find_dest (pdf_document->document, + link_name); + if (dest) { + ev_dest = ev_link_dest_from_dest (pdf_document, dest); + poppler_dest_free (dest); + } + + return ev_dest; +} + +static void +pdf_document_document_links_iface_init (EvDocumentLinksInterface *iface) +{ + iface->has_document_links = pdf_document_links_has_document_links; + iface->get_links_model = pdf_document_links_get_links_model; + iface->get_links = pdf_document_links_get_links; + iface->find_link_dest = pdf_document_links_find_link_dest; +} + +static EvMappingList * +pdf_document_images_get_image_mapping (EvDocumentImages *document_images, + EvPage *page) +{ + GList *retval = NULL; + PdfDocument *pdf_document; + PopplerPage *poppler_page; + GList *mapping_list; + GList *list; + + pdf_document = PDF_DOCUMENT (document_images); + poppler_page = POPPLER_PAGE (page->backend_page); + mapping_list = poppler_page_get_image_mapping (poppler_page); + + for (list = mapping_list; list; list = list->next) { + PopplerImageMapping *image_mapping; + EvMapping *ev_image_mapping; + + image_mapping = (PopplerImageMapping *)list->data; + + ev_image_mapping = g_new (EvMapping, 1); + + ev_image_mapping->data = ev_image_new (page->index, image_mapping->image_id); + ev_image_mapping->area.x1 = image_mapping->area.x1; + ev_image_mapping->area.y1 = image_mapping->area.y1; + ev_image_mapping->area.x2 = image_mapping->area.x2; + ev_image_mapping->area.y2 = image_mapping->area.y2; + + retval = g_list_prepend (retval, ev_image_mapping); + } + + poppler_page_free_image_mapping (mapping_list); + + return ev_mapping_list_new (page->index, g_list_reverse (retval), (GDestroyNotify)g_object_unref); +} + +GdkPixbuf * +pdf_document_images_get_image (EvDocumentImages *document_images, + EvImage *image) +{ + GdkPixbuf *retval = NULL; + PdfDocument *pdf_document; + PopplerPage *poppler_page; + cairo_surface_t *surface; + + pdf_document = PDF_DOCUMENT (document_images); + poppler_page = poppler_document_get_page (pdf_document->document, + ev_image_get_page (image)); + + surface = poppler_page_get_image (poppler_page, ev_image_get_id (image)); + if (surface) { + retval = ev_document_misc_pixbuf_from_surface (surface); + cairo_surface_destroy (surface); + } + + g_object_unref (poppler_page); + + return retval; +} + +static void +pdf_document_document_images_iface_init (EvDocumentImagesInterface *iface) +{ + iface->get_image_mapping = pdf_document_images_get_image_mapping; + iface->get_image = pdf_document_images_get_image; +} + +static GdkPixbuf * +make_thumbnail_for_page (PopplerPage *poppler_page, + EvRenderContext *rc, + gint width, + gint height) +{ + GdkPixbuf *pixbuf; + +#ifdef POPPLER_WITH_GDK + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, + width, height); + gdk_pixbuf_fill (pixbuf, 0xffffffff); + + ev_document_fc_mutex_lock (); + poppler_page_render_to_pixbuf (poppler_page, 0, 0, + width, height, + rc->scale, rc->rotation, pixbuf); + ev_document_fc_mutex_unlock (); +#else + cairo_surface_t *surface; + + ev_document_fc_mutex_lock (); + surface = pdf_page_render (poppler_page, width, height, rc); + ev_document_fc_mutex_unlock (); + + pixbuf = ev_document_misc_pixbuf_from_surface (surface); + cairo_surface_destroy (surface); +#endif /* POPPLER_WITH_GDK */ + + return pixbuf; +} + +static GdkPixbuf * +pdf_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document_thumbnails, + EvRenderContext *rc, + gboolean border) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document_thumbnails); + PopplerPage *poppler_page; + GdkPixbuf *pixbuf = NULL; + GdkPixbuf *border_pixbuf; + gint width, height; + + poppler_page = POPPLER_PAGE (rc->page->backend_page); + + pdf_document_thumbnails_get_dimensions (EV_DOCUMENT_THUMBNAILS (pdf_document), + rc, &width, &height); + +#ifdef POPPLER_WITH_GDK + pixbuf = poppler_page_get_thumbnail_pixbuf (poppler_page); +#else + cairo_surface_t *surface; + + surface = poppler_page_get_thumbnail (poppler_page); + if (surface) { + pixbuf = ev_document_misc_pixbuf_from_surface (surface); + cairo_surface_destroy (surface); + } +#endif /* POPPLER_WITH_GDK */ + + if (pixbuf != NULL) { + int thumb_width = (rc->rotation == 90 || rc->rotation == 270) ? + gdk_pixbuf_get_height (pixbuf) : + gdk_pixbuf_get_width (pixbuf); + + if (thumb_width == width) { + GdkPixbuf *rotated_pixbuf; + + rotated_pixbuf = gdk_pixbuf_rotate_simple (pixbuf, + (GdkPixbufRotation) (360 - rc->rotation)); + g_object_unref (pixbuf); + pixbuf = rotated_pixbuf; + } else { + /* The provided thumbnail has a different size */ + g_object_unref (pixbuf); + pixbuf = make_thumbnail_for_page (poppler_page, rc, width, height); + } + } else { + /* There is no provided thumbnail. We need to make one. */ + pixbuf = make_thumbnail_for_page (poppler_page, rc, width, height); + } + + if (border && pixbuf) { + border_pixbuf = ev_document_misc_get_thumbnail_frame (-1, -1, pixbuf); + g_object_unref (pixbuf); + pixbuf = border_pixbuf; + } + + return pixbuf; +} + +static void +pdf_document_thumbnails_get_dimensions (EvDocumentThumbnails *document_thumbnails, + EvRenderContext *rc, + gint *width, + gint *height) +{ + double page_width, page_height; + + poppler_page_get_size (POPPLER_PAGE (rc->page->backend_page), + &page_width, &page_height); + + *width = MAX ((gint)(page_width * rc->scale + 0.5), 1); + *height = MAX ((gint)(page_height * rc->scale + 0.5), 1); + + if (rc->rotation == 90 || rc->rotation == 270) { + gint temp; + + temp = *width; + *width = *height; + *height = temp; + } +} + +static void +pdf_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface) +{ + iface->get_thumbnail = pdf_document_thumbnails_get_thumbnail; + iface->get_dimensions = pdf_document_thumbnails_get_dimensions; +} + + +static GList * +pdf_document_find_find_text (EvDocumentFind *document_find, + EvPage *page, + const gchar *text, + gboolean case_sensitive) +{ + GList *matches, *l; + PopplerPage *poppler_page; + gdouble height; + GList *retval = NULL; + + g_return_val_if_fail (POPPLER_IS_PAGE (page->backend_page), NULL); + g_return_val_if_fail (text != NULL, NULL); + + poppler_page = POPPLER_PAGE (page->backend_page); + + matches = poppler_page_find_text (poppler_page, text); + if (!matches) + return NULL; + + poppler_page_get_size (poppler_page, NULL, &height); + for (l = matches; l && l->data; l = g_list_next (l)) { + PopplerRectangle *rect = (PopplerRectangle *)l->data; + EvRectangle *ev_rect; + + ev_rect = ev_rectangle_new (); + ev_rect->x1 = rect->x1; + ev_rect->x2 = rect->x2; + /* Invert this for X-style coordinates */ + ev_rect->y1 = height - rect->y2; + ev_rect->y2 = height - rect->y1; + + retval = g_list_prepend (retval, ev_rect); + } + + g_list_foreach (matches, (GFunc)poppler_rectangle_free, NULL); + g_list_free (matches); + + return g_list_reverse (retval); +} + +static void +pdf_document_find_iface_init (EvDocumentFindInterface *iface) +{ + iface->find_text = pdf_document_find_find_text; +} + +static void +pdf_print_context_free (PdfPrintContext *ctx) +{ + if (!ctx) + return; + +#ifdef HAVE_CAIRO_PRINT + if (ctx->cr) { + cairo_destroy (ctx->cr); + ctx->cr = NULL; + } +#else + if (ctx->ps_file) { + poppler_ps_file_free (ctx->ps_file); + ctx->ps_file = NULL; + } +#endif + g_free (ctx); +} + +static void +pdf_document_file_exporter_begin (EvFileExporter *exporter, + EvFileExporterContext *fc) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (exporter); + PdfPrintContext *ctx; +#ifdef HAVE_CAIRO_PRINT + gdouble width, height; + cairo_surface_t *surface = NULL; +#endif + + if (pdf_document->print_ctx) + pdf_print_context_free (pdf_document->print_ctx); + pdf_document->print_ctx = g_new0 (PdfPrintContext, 1); + ctx = pdf_document->print_ctx; + ctx->format = fc->format; + +#ifdef HAVE_CAIRO_PRINT + ctx->pages_per_sheet = CLAMP (fc->pages_per_sheet, 1, 16); + + ctx->paper_width = fc->paper_width; + ctx->paper_height = fc->paper_height; + + switch (fc->pages_per_sheet) { + default: + case 1: + ctx->pages_x = 1; + ctx->pages_y = 1; + break; + case 2: + ctx->pages_x = 1; + ctx->pages_y = 2; + break; + case 4: + ctx->pages_x = 2; + ctx->pages_y = 2; + break; + case 6: + ctx->pages_x = 2; + ctx->pages_y = 3; + break; + case 9: + ctx->pages_x = 3; + ctx->pages_y = 3; + break; + case 16: + ctx->pages_x = 4; + ctx->pages_y = 4; + break; + } + + ctx->pages_printed = 0; + + switch (fc->format) { + case EV_FILE_FORMAT_PS: +#ifdef HAVE_CAIRO_PS + surface = cairo_ps_surface_create (fc->filename, fc->paper_width, fc->paper_height); +#endif + break; + case EV_FILE_FORMAT_PDF: +#ifdef HAVE_CAIRO_PDF + surface = cairo_pdf_surface_create (fc->filename, fc->paper_width, fc->paper_height); +#endif + break; + default: + g_assert_not_reached (); + } + + ctx->cr = cairo_create (surface); + cairo_surface_destroy (surface); + +#else /* HAVE_CAIRO_PRINT */ + if (ctx->format == EV_FILE_FORMAT_PS) { + ctx->ps_file = poppler_ps_file_new (pdf_document->document, + fc->filename, fc->first_page, + fc->last_page - fc->first_page + 1); + poppler_ps_file_set_paper_size (ctx->ps_file, fc->paper_width, fc->paper_height); + poppler_ps_file_set_duplex (ctx->ps_file, fc->duplex); + } +#endif /* HAVE_CAIRO_PRINT */ +} + +static void +pdf_document_file_exporter_begin_page (EvFileExporter *exporter) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (exporter); + PdfPrintContext *ctx = pdf_document->print_ctx; + + g_return_if_fail (pdf_document->print_ctx != NULL); + + ctx->pages_printed = 0; + +#ifdef HAVE_CAIRO_PRINT + if (ctx->paper_width > ctx->paper_height) { + if (ctx->format == EV_FILE_FORMAT_PS) { + cairo_ps_surface_set_size (cairo_get_target (ctx->cr), + ctx->paper_height, + ctx->paper_width); + } else if (ctx->format == EV_FILE_FORMAT_PDF) { + cairo_pdf_surface_set_size (cairo_get_target (ctx->cr), + ctx->paper_height, + ctx->paper_width); + } + } +#endif /* HAVE_CAIRO_PRINT */ +} + +static void +pdf_document_file_exporter_do_page (EvFileExporter *exporter, + EvRenderContext *rc) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (exporter); + PdfPrintContext *ctx = pdf_document->print_ctx; + PopplerPage *poppler_page; +#ifdef HAVE_CAIRO_PRINT + gdouble page_width, page_height; + gint x, y; + gboolean rotate; + gdouble width, height; + gdouble pwidth, pheight; + gdouble xscale, yscale; +#endif + + g_return_if_fail (pdf_document->print_ctx != NULL); + + poppler_page = POPPLER_PAGE (rc->page->backend_page); + +#ifdef HAVE_CAIRO_PRINT + x = (ctx->pages_printed % ctx->pages_per_sheet) % ctx->pages_x; + y = (ctx->pages_printed % ctx->pages_per_sheet) / ctx->pages_x; + poppler_page_get_size (poppler_page, &page_width, &page_height); + + if (page_width > page_height && page_width > ctx->paper_width) { + rotate = TRUE; + } else { + rotate = FALSE; + } + + /* Use always portrait mode and rotate when necessary */ + if (ctx->paper_width > ctx->paper_height) { + width = ctx->paper_height; + height = ctx->paper_width; + rotate = !rotate; + } else { + width = ctx->paper_width; + height = ctx->paper_height; + } + + if (ctx->pages_per_sheet == 2 || ctx->pages_per_sheet == 6) { + rotate = !rotate; + } + + if (rotate) { + gint tmp1; + gdouble tmp2; + + tmp1 = x; + x = y; + y = tmp1; + + tmp2 = page_width; + page_width = page_height; + page_height = tmp2; + } + + pwidth = width / ctx->pages_x; + pheight = height / ctx->pages_y; + + if ((page_width > pwidth || page_height > pheight) || + (page_width < pwidth && page_height < pheight)) { + xscale = pwidth / page_width; + yscale = pheight / page_height; + + if (yscale < xscale) { + xscale = yscale; + } else { + yscale = xscale; + } + + } else { + xscale = yscale = 1; + } + + /* TODO: center */ + + cairo_save (ctx->cr); + if (rotate) { + cairo_matrix_t matrix; + + cairo_translate (ctx->cr, (2 * y + 1) * pwidth, 0); + cairo_matrix_init (&matrix, + 0, 1, + -1, 0, + 0, 0); + cairo_transform (ctx->cr, &matrix); + } + + cairo_translate (ctx->cr, + x * (rotate ? pheight : pwidth), + y * (rotate ? pwidth : pheight)); + cairo_scale (ctx->cr, xscale, yscale); + + poppler_page_render_for_printing (poppler_page, ctx->cr); + + ctx->pages_printed++; + + cairo_restore (ctx->cr); +#else /* HAVE_CAIRO_PRINT */ + if (ctx->format == EV_FILE_FORMAT_PS) + poppler_page_render_to_ps (poppler_page, ctx->ps_file); +#endif /* HAVE_CAIRO_PRINT */ +} + +static void +pdf_document_file_exporter_end_page (EvFileExporter *exporter) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (exporter); + PdfPrintContext *ctx = pdf_document->print_ctx; + + g_return_if_fail (pdf_document->print_ctx != NULL); + +#ifdef HAVE_CAIRO_PRINT + cairo_show_page (ctx->cr); +#endif +} + +static void +pdf_document_file_exporter_end (EvFileExporter *exporter) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (exporter); + + pdf_print_context_free (pdf_document->print_ctx); + pdf_document->print_ctx = NULL; +} + +static EvFileExporterCapabilities +pdf_document_file_exporter_get_capabilities (EvFileExporter *exporter) +{ + return (EvFileExporterCapabilities) ( + EV_FILE_EXPORTER_CAN_PAGE_SET | + EV_FILE_EXPORTER_CAN_COPIES | + EV_FILE_EXPORTER_CAN_COLLATE | + EV_FILE_EXPORTER_CAN_REVERSE | + EV_FILE_EXPORTER_CAN_SCALE | +#ifdef HAVE_CAIRO_PRINT + EV_FILE_EXPORTER_CAN_NUMBER_UP | +#endif + +#ifdef HAVE_CAIRO_PDF + EV_FILE_EXPORTER_CAN_GENERATE_PDF | +#endif + EV_FILE_EXPORTER_CAN_GENERATE_PS); +} + +static void +pdf_document_file_exporter_iface_init (EvFileExporterInterface *iface) +{ + iface->begin = pdf_document_file_exporter_begin; + iface->begin_page = pdf_document_file_exporter_begin_page; + iface->do_page = pdf_document_file_exporter_do_page; + iface->end_page = pdf_document_file_exporter_end_page; + iface->end = pdf_document_file_exporter_end; + iface->get_capabilities = pdf_document_file_exporter_get_capabilities; +} + +/* EvDocumentPrint */ +static void +pdf_document_print_print_page (EvDocumentPrint *document, + EvPage *page, + cairo_t *cr) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document); + + poppler_page_render_for_printing (POPPLER_PAGE (page->backend_page), cr); +} + +static void +pdf_document_document_print_iface_init (EvDocumentPrintInterface *iface) +{ + iface->print_page = pdf_document_print_print_page; +} + +static void +pdf_selection_render_selection (EvSelection *selection, + EvRenderContext *rc, + cairo_surface_t **surface, + EvRectangle *points, + EvRectangle *old_points, + EvSelectionStyle style, + GdkColor *text, + GdkColor *base) +{ + PopplerPage *poppler_page; + cairo_t *cr; + PopplerColor text_color, base_color; + double width_points, height_points; + gint width, height; + + poppler_page = POPPLER_PAGE (rc->page->backend_page); + + poppler_page_get_size (poppler_page, + &width_points, &height_points); + width = (int) ((width_points * rc->scale) + 0.5); + height = (int) ((height_points * rc->scale) + 0.5); + + text_color.red = text->red; + text_color.green = text->green; + text_color.blue = text->blue; + + base_color.red = base->red; + base_color.green = base->green; + base_color.blue = base->blue; + + if (*surface == NULL) { + *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + width, height); + + } + + cr = cairo_create (*surface); + cairo_scale (cr, rc->scale, rc->scale); + cairo_surface_set_device_offset (*surface, 0, 0); + memset (cairo_image_surface_get_data (*surface), 0x00, + cairo_image_surface_get_height (*surface) * + cairo_image_surface_get_stride (*surface)); + poppler_page_render_selection (poppler_page, + cr, + (PopplerRectangle *)points, + (PopplerRectangle *)old_points, + (PopplerSelectionStyle)style, + &text_color, + &base_color); + cairo_destroy (cr); +} + +static gchar * +pdf_selection_get_selected_text (EvSelection *selection, + EvPage *page, + EvSelectionStyle style, + EvRectangle *points) +{ + PopplerPage *poppler_page; + char *retval; + + poppler_page = POPPLER_PAGE (page->backend_page); + +#ifdef HAVE_POPPLER_PAGE_GET_SELECTED_TEXT + retval = poppler_page_get_selected_text (poppler_page, + (PopplerSelectionStyle)style, + (PopplerRectangle *)points); +#else + PopplerRectangle r; + double height; + + poppler_page_get_size (poppler_page, NULL, &height); + r.x1 = points->x1; + r.y1 = height - points->y2; + r.x2 = points->x2; + r.y2 = height - points->y1; + + retval = poppler_page_get_text (poppler_page, + (PopplerSelectionStyle)style, + &r); +#endif /* HAVE_POPPLER_PAGE_GET_SELECTED_TEXT */ + + return retval; +} + +static cairo_region_t * +create_region_from_poppler_region (GList *region, gdouble scale) +{ + GList *l; + cairo_region_t *retval; + + retval = cairo_region_create (); + + for (l = region; l; l = g_list_next (l)) { + PopplerRectangle *rectangle; + cairo_rectangle_int_t rect; + + rectangle = (PopplerRectangle *)l->data; + + rect.x = (gint) ((rectangle->x1 * scale) + 0.5); + rect.y = (gint) ((rectangle->y1 * scale) + 0.5); + rect.width = (gint) (((rectangle->x2 - rectangle->x1) * scale) + 0.5); + rect.height = (gint) (((rectangle->y2 - rectangle->y1) * scale) + 0.5); + cairo_region_union_rectangle (retval, &rect); + + poppler_rectangle_free (rectangle); + } + + return retval; +} + +static cairo_region_t * +pdf_selection_get_selection_region (EvSelection *selection, + EvRenderContext *rc, + EvSelectionStyle style, + EvRectangle *points) +{ + PopplerPage *poppler_page; + cairo_region_t *retval; + GList *region; + + poppler_page = POPPLER_PAGE (rc->page->backend_page); + region = poppler_page_get_selection_region (poppler_page, + 1.0, + (PopplerSelectionStyle)style, + (PopplerRectangle *) points); + retval = create_region_from_poppler_region (region, rc->scale); + g_list_free (region); + + return retval; +} + +static void +pdf_selection_iface_init (EvSelectionInterface *iface) +{ + iface->render_selection = pdf_selection_render_selection; + iface->get_selected_text = pdf_selection_get_selected_text; + iface->get_selection_region = pdf_selection_get_selection_region; +} + + +/* EvDocumentText */ +static cairo_region_t * +pdf_document_text_get_text_mapping (EvDocumentText *document_text, + EvPage *page) +{ + PopplerPage *poppler_page; + PopplerRectangle points; + GList *region; + cairo_region_t *retval; + + g_return_val_if_fail (POPPLER_IS_PAGE (page->backend_page), NULL); + + poppler_page = POPPLER_PAGE (page->backend_page); + + points.x1 = 0.0; + points.y1 = 0.0; + poppler_page_get_size (poppler_page, &(points.x2), &(points.y2)); + + region = poppler_page_get_selection_region (poppler_page, 1.0, + POPPLER_SELECTION_GLYPH, + &points); + retval = create_region_from_poppler_region (region, 1.0); + g_list_free (region); + + return retval; +} + +#ifdef HAVE_POPPLER_PAGE_GET_SELECTED_TEXT +static gchar * +pdf_document_text_get_text (EvDocumentText *selection, + EvPage *page) +{ + PopplerPage *poppler_page; + + g_return_val_if_fail (POPPLER_IS_PAGE (page->backend_page), NULL); + + poppler_page = POPPLER_PAGE (page->backend_page); + + return poppler_page_get_text (poppler_page); +} +#else +static gchar * +pdf_document_text_get_text (EvDocumentText *selection, + EvPage *page) +{ + PopplerPage *poppler_page; + PopplerRectangle r; + + g_return_val_if_fail (POPPLER_IS_PAGE (page->backend_page), NULL); + + poppler_page = POPPLER_PAGE (page->backend_page); + + r.x1 = 0; + r.y1 = 0; + poppler_page_get_size (poppler_page, &(r.x2), &(r.y2)); + + return poppler_page_get_text (poppler_page, + POPPLER_SELECTION_WORD, + &r); +} +#endif /* HAVE_POPPLER_PAGE_GET_SELECTED_TEXT */ + +#ifdef HAVE_POPPLER_PAGE_GET_TEXT_LAYOUT +static gboolean +pdf_document_text_get_text_layout (EvDocumentText *selection, + EvPage *page, + EvRectangle **areas, + guint *n_areas) +{ + PopplerPage *poppler_page; + + g_return_val_if_fail (POPPLER_IS_PAGE (page->backend_page), NULL); + + poppler_page = POPPLER_PAGE (page->backend_page); + + return poppler_page_get_text_layout (poppler_page, (PopplerRectangle **)areas, n_areas); +} +#endif + +static void +pdf_document_text_iface_init (EvDocumentTextInterface *iface) +{ + iface->get_text_mapping = pdf_document_text_get_text_mapping; + iface->get_text = pdf_document_text_get_text; +#ifdef HAVE_POPPLER_PAGE_GET_TEXT_LAYOUT + iface->get_text_layout = pdf_document_text_get_text_layout; +#endif +} + +/* Page Transitions */ +static gdouble +pdf_document_get_page_duration (EvDocumentTransition *trans, + gint page) +{ + PdfDocument *pdf_document; + PopplerPage *poppler_page; + gdouble duration = -1; + + pdf_document = PDF_DOCUMENT (trans); + poppler_page = poppler_document_get_page (pdf_document->document, page); + if (!poppler_page) + return -1; + + duration = poppler_page_get_duration (poppler_page); + g_object_unref (poppler_page); + + return duration; +} + +static EvTransitionEffect * +pdf_document_get_effect (EvDocumentTransition *trans, + gint page) +{ + PdfDocument *pdf_document; + PopplerPage *poppler_page; + PopplerPageTransition *page_transition; + EvTransitionEffect *effect; + + pdf_document = PDF_DOCUMENT (trans); + poppler_page = poppler_document_get_page (pdf_document->document, page); + + if (!poppler_page) + return NULL; + + page_transition = poppler_page_get_transition (poppler_page); + + if (!page_transition) { + g_object_unref (poppler_page); + return NULL; + } + + /* enums in PopplerPageTransition match the EvTransitionEffect ones */ + effect = ev_transition_effect_new ((EvTransitionEffectType) page_transition->type, + "alignment", page_transition->alignment, + "direction", page_transition->direction, + "duration", page_transition->duration, + "angle", page_transition->angle, + "scale", page_transition->scale, + "rectangular", page_transition->rectangular, + NULL); + + poppler_page_transition_free (page_transition); + g_object_unref (poppler_page); + + return effect; +} + +static void +pdf_document_page_transition_iface_init (EvDocumentTransitionInterface *iface) +{ + iface->get_page_duration = pdf_document_get_page_duration; + iface->get_effect = pdf_document_get_effect; +} + +/* Forms */ +static void +pdf_document_get_crop_box (EvDocument *document, + int page, + EvRectangle *rect) +{ + PdfDocument *pdf_document; + PopplerPage *poppler_page; + PopplerRectangle poppler_rect; + + pdf_document = PDF_DOCUMENT (document); + poppler_page = poppler_document_get_page (pdf_document->document, page); + poppler_page_get_crop_box (poppler_page, &poppler_rect); + rect->x1 = poppler_rect.x1; + rect->x2 = poppler_rect.x2; + rect->y1 = poppler_rect.y1; + rect->y2 = poppler_rect.y2; +} + +static EvFormField * +ev_form_field_from_poppler_field (PopplerFormField *poppler_field) +{ + EvFormField *ev_field = NULL; + gint id; + gdouble font_size; + gboolean is_read_only; + + id = poppler_form_field_get_id (poppler_field); + font_size = poppler_form_field_get_font_size (poppler_field); + is_read_only = poppler_form_field_is_read_only (poppler_field); + + switch (poppler_form_field_get_field_type (poppler_field)) { + case POPPLER_FORM_FIELD_TEXT: { + EvFormFieldText *field_text; + EvFormFieldTextType ev_text_type = EV_FORM_FIELD_TEXT_NORMAL; + + switch (poppler_form_field_text_get_text_type (poppler_field)) { + case POPPLER_FORM_TEXT_NORMAL: + ev_text_type = EV_FORM_FIELD_TEXT_NORMAL; + break; + case POPPLER_FORM_TEXT_MULTILINE: + ev_text_type = EV_FORM_FIELD_TEXT_MULTILINE; + break; + case POPPLER_FORM_TEXT_FILE_SELECT: + ev_text_type = EV_FORM_FIELD_TEXT_FILE_SELECT; + break; + } + + ev_field = ev_form_field_text_new (id, ev_text_type); + field_text = EV_FORM_FIELD_TEXT (ev_field); + + field_text->do_spell_check = poppler_form_field_text_do_spell_check (poppler_field); + field_text->do_scroll = poppler_form_field_text_do_scroll (poppler_field); + field_text->is_rich_text = poppler_form_field_text_is_rich_text (poppler_field); + field_text->is_password = poppler_form_field_text_is_password (poppler_field); + field_text->max_len = poppler_form_field_text_get_max_len (poppler_field); + field_text->text = poppler_form_field_text_get_text (poppler_field); + + } + break; + case POPPLER_FORM_FIELD_BUTTON: { + EvFormFieldButton *field_button; + EvFormFieldButtonType ev_button_type = EV_FORM_FIELD_BUTTON_PUSH; + + switch (poppler_form_field_button_get_button_type (poppler_field)) { + case POPPLER_FORM_BUTTON_PUSH: + ev_button_type = EV_FORM_FIELD_BUTTON_PUSH; + break; + case POPPLER_FORM_BUTTON_CHECK: + ev_button_type = EV_FORM_FIELD_BUTTON_CHECK; + break; + case POPPLER_FORM_BUTTON_RADIO: + ev_button_type = EV_FORM_FIELD_BUTTON_RADIO; + break; + } + + ev_field = ev_form_field_button_new (id, ev_button_type); + field_button = EV_FORM_FIELD_BUTTON (ev_field); + + field_button->state = poppler_form_field_button_get_state (poppler_field); + } + break; + case POPPLER_FORM_FIELD_CHOICE: { + EvFormFieldChoice *field_choice; + EvFormFieldChoiceType ev_choice_type = EV_FORM_FIELD_CHOICE_COMBO; + + switch (poppler_form_field_choice_get_choice_type (poppler_field)) { + case POPPLER_FORM_CHOICE_COMBO: + ev_choice_type = EV_FORM_FIELD_CHOICE_COMBO; + break; + case EV_FORM_FIELD_CHOICE_LIST: + ev_choice_type = EV_FORM_FIELD_CHOICE_LIST; + break; + } + + ev_field = ev_form_field_choice_new (id, ev_choice_type); + field_choice = EV_FORM_FIELD_CHOICE (ev_field); + + field_choice->is_editable = poppler_form_field_choice_is_editable (poppler_field); + field_choice->multi_select = poppler_form_field_choice_can_select_multiple (poppler_field); + field_choice->do_spell_check = poppler_form_field_choice_do_spell_check (poppler_field); + field_choice->commit_on_sel_change = poppler_form_field_choice_commit_on_change (poppler_field); + + /* TODO: we need poppler_form_field_choice_get_selected_items in poppler + field_choice->selected_items = poppler_form_field_choice_get_selected_items (poppler_field);*/ + if (field_choice->is_editable) + field_choice->text = poppler_form_field_choice_get_text (poppler_field); + } + break; + case POPPLER_FORM_FIELD_SIGNATURE: + /* TODO */ + ev_field = ev_form_field_signature_new (id); + break; + case POPPLER_FORM_FIELD_UNKNOWN: + return NULL; + } + + ev_field->font_size = font_size; + ev_field->is_read_only = is_read_only; + + return ev_field; +} + +static EvMappingList * +pdf_document_forms_get_form_fields (EvDocumentForms *document, + EvPage *page) +{ + PopplerPage *poppler_page; + GList *retval = NULL; + GList *fields; + GList *list; + double height; + + g_return_val_if_fail (POPPLER_IS_PAGE (page->backend_page), NULL); + + poppler_page = POPPLER_PAGE (page->backend_page); + fields = poppler_page_get_form_field_mapping (poppler_page); + poppler_page_get_size (poppler_page, NULL, &height); + + for (list = fields; list; list = list->next) { + PopplerFormFieldMapping *mapping; + EvMapping *field_mapping; + EvFormField *ev_field; + + mapping = (PopplerFormFieldMapping *)list->data; + + ev_field = ev_form_field_from_poppler_field (mapping->field); + if (!ev_field) + continue; + + field_mapping = g_new0 (EvMapping, 1); + field_mapping->area.x1 = mapping->area.x1; + field_mapping->area.x2 = mapping->area.x2; + field_mapping->area.y1 = height - mapping->area.y2; + field_mapping->area.y2 = height - mapping->area.y1; + field_mapping->data = ev_field; + ev_field->page = EV_PAGE (g_object_ref (page)); + + g_object_set_data_full (G_OBJECT (ev_field), + "poppler-field", + g_object_ref (mapping->field), + (GDestroyNotify) g_object_unref); + + retval = g_list_prepend (retval, field_mapping); + } + + poppler_page_free_form_field_mapping (fields); + + return retval ? ev_mapping_list_new (page->index, + g_list_reverse (retval), + (GDestroyNotify)g_object_unref) : NULL; +} + +static gboolean +pdf_document_forms_document_is_modified (EvDocumentForms *document) +{ + return PDF_DOCUMENT (document)->forms_modified; +} + +static gchar * +pdf_document_forms_form_field_text_get_text (EvDocumentForms *document, + EvFormField *field) + +{ + PopplerFormField *poppler_field; + gchar *text; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return NULL; + + text = poppler_form_field_text_get_text (poppler_field); + + return text; +} + +static void +pdf_document_forms_form_field_text_set_text (EvDocumentForms *document, + EvFormField *field, + const gchar *text) +{ + PopplerFormField *poppler_field; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return; + + poppler_form_field_text_set_text (poppler_field, text); + PDF_DOCUMENT (document)->forms_modified = TRUE; +} + +static void +pdf_document_forms_form_field_button_set_state (EvDocumentForms *document, + EvFormField *field, + gboolean state) +{ + PopplerFormField *poppler_field; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return; + + poppler_form_field_button_set_state (poppler_field, state); + PDF_DOCUMENT (document)->forms_modified = TRUE; +} + +static gboolean +pdf_document_forms_form_field_button_get_state (EvDocumentForms *document, + EvFormField *field) +{ + PopplerFormField *poppler_field; + gboolean state; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return FALSE; + + state = poppler_form_field_button_get_state (poppler_field); + + return state; +} + +static gchar * +pdf_document_forms_form_field_choice_get_item (EvDocumentForms *document, + EvFormField *field, + gint index) +{ + PopplerFormField *poppler_field; + gchar *text; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return NULL; + + text = poppler_form_field_choice_get_item (poppler_field, index); + + return text; +} + +static int +pdf_document_forms_form_field_choice_get_n_items (EvDocumentForms *document, + EvFormField *field) +{ + PopplerFormField *poppler_field; + gint n_items; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return -1; + + n_items = poppler_form_field_choice_get_n_items (poppler_field); + + return n_items; +} + +static gboolean +pdf_document_forms_form_field_choice_is_item_selected (EvDocumentForms *document, + EvFormField *field, + gint index) +{ + PopplerFormField *poppler_field; + gboolean selected; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return FALSE; + + selected = poppler_form_field_choice_is_item_selected (poppler_field, index); + + return selected; +} + +static void +pdf_document_forms_form_field_choice_select_item (EvDocumentForms *document, + EvFormField *field, + gint index) +{ + PopplerFormField *poppler_field; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return; + + poppler_form_field_choice_select_item (poppler_field, index); + PDF_DOCUMENT (document)->forms_modified = TRUE; +} + +static void +pdf_document_forms_form_field_choice_toggle_item (EvDocumentForms *document, + EvFormField *field, + gint index) +{ + PopplerFormField *poppler_field; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return; + + poppler_form_field_choice_toggle_item (poppler_field, index); + PDF_DOCUMENT (document)->forms_modified = TRUE; +} + +static void +pdf_document_forms_form_field_choice_unselect_all (EvDocumentForms *document, + EvFormField *field) +{ + PopplerFormField *poppler_field; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return; + + poppler_form_field_choice_unselect_all (poppler_field); + PDF_DOCUMENT (document)->forms_modified = TRUE; +} + +static void +pdf_document_forms_form_field_choice_set_text (EvDocumentForms *document, + EvFormField *field, + const gchar *text) +{ + PopplerFormField *poppler_field; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return; + + poppler_form_field_choice_set_text (poppler_field, text); + PDF_DOCUMENT (document)->forms_modified = TRUE; +} + +static gchar * +pdf_document_forms_form_field_choice_get_text (EvDocumentForms *document, + EvFormField *field) +{ + PopplerFormField *poppler_field; + gchar *text; + + poppler_field = POPPLER_FORM_FIELD (g_object_get_data (G_OBJECT (field), "poppler-field")); + if (!poppler_field) + return NULL; + + text = poppler_form_field_choice_get_text (poppler_field); + + return text; +} + +static void +pdf_document_document_forms_iface_init (EvDocumentFormsInterface *iface) +{ + iface->get_form_fields = pdf_document_forms_get_form_fields; + iface->document_is_modified = pdf_document_forms_document_is_modified; + iface->form_field_text_get_text = pdf_document_forms_form_field_text_get_text; + iface->form_field_text_set_text = pdf_document_forms_form_field_text_set_text; + iface->form_field_button_set_state = pdf_document_forms_form_field_button_set_state; + iface->form_field_button_get_state = pdf_document_forms_form_field_button_get_state; + iface->form_field_choice_get_item = pdf_document_forms_form_field_choice_get_item; + iface->form_field_choice_get_n_items = pdf_document_forms_form_field_choice_get_n_items; + iface->form_field_choice_is_item_selected = pdf_document_forms_form_field_choice_is_item_selected; + iface->form_field_choice_select_item = pdf_document_forms_form_field_choice_select_item; + iface->form_field_choice_toggle_item = pdf_document_forms_form_field_choice_toggle_item; + iface->form_field_choice_unselect_all = pdf_document_forms_form_field_choice_unselect_all; + iface->form_field_choice_set_text = pdf_document_forms_form_field_choice_set_text; + iface->form_field_choice_get_text = pdf_document_forms_form_field_choice_get_text; +} + +/* Annotations */ +static void +poppler_annot_color_to_gdk_color (PopplerAnnot *poppler_annot, + GdkColor *color) +{ + PopplerColor *poppler_color; + + poppler_color = poppler_annot_get_color (poppler_annot); + if (poppler_color) { + color->red = poppler_color->red; + color->green = poppler_color->green; + color->blue = poppler_color->blue; + + g_free (poppler_color); + } /* TODO: else use a default color */ +} + +static EvAnnotationTextIcon +get_annot_text_icon (PopplerAnnotText *poppler_annot) +{ +#ifdef HAVE_POPPLER_PAGE_ADD_ANNOT + gchar *icon = poppler_annot_text_get_icon (poppler_annot); + EvAnnotationTextIcon retval; + + if (!icon) + return EV_ANNOTATION_TEXT_ICON_UNKNOWN; + + if (strcmp (icon, POPPLER_ANNOT_TEXT_ICON_NOTE) == 0) + retval = EV_ANNOTATION_TEXT_ICON_NOTE; + else if (strcmp (icon, POPPLER_ANNOT_TEXT_ICON_COMMENT) == 0) + retval = EV_ANNOTATION_TEXT_ICON_COMMENT; + else if (strcmp (icon, POPPLER_ANNOT_TEXT_ICON_KEY) == 0) + retval = EV_ANNOTATION_TEXT_ICON_KEY; + else if (strcmp (icon, POPPLER_ANNOT_TEXT_ICON_HELP) == 0) + retval = EV_ANNOTATION_TEXT_ICON_HELP; + else if (strcmp (icon, POPPLER_ANNOT_TEXT_ICON_NEW_PARAGRAPH) == 0) + retval = EV_ANNOTATION_TEXT_ICON_NEW_PARAGRAPH; + else if (strcmp (icon, POPPLER_ANNOT_TEXT_ICON_PARAGRAPH) == 0) + retval = EV_ANNOTATION_TEXT_ICON_PARAGRAPH; + else if (strcmp (icon, POPPLER_ANNOT_TEXT_ICON_INSERT) == 0) + retval = EV_ANNOTATION_TEXT_ICON_INSERT; + else if (strcmp (icon, POPPLER_ANNOT_TEXT_ICON_CROSS) == 0) + retval = EV_ANNOTATION_TEXT_ICON_CROSS; + else if (strcmp (icon, POPPLER_ANNOT_TEXT_ICON_CIRCLE) == 0) + retval = EV_ANNOTATION_TEXT_ICON_CIRCLE; + else + retval = EV_ANNOTATION_TEXT_ICON_UNKNOWN; + + g_free (icon); + + return retval; +#else + return EV_ANNOTATION_TEXT_ICON_UNKNOWN; +#endif +} + +static const gchar * +get_poppler_annot_text_icon (EvAnnotationTextIcon icon) +{ +#ifdef HAVE_POPPLER_PAGE_ADD_ANNOT + switch (icon) { + case EV_ANNOTATION_TEXT_ICON_NOTE: + return POPPLER_ANNOT_TEXT_ICON_NOTE; + case EV_ANNOTATION_TEXT_ICON_COMMENT: + return POPPLER_ANNOT_TEXT_ICON_COMMENT; + case EV_ANNOTATION_TEXT_ICON_KEY: + return POPPLER_ANNOT_TEXT_ICON_KEY; + case EV_ANNOTATION_TEXT_ICON_HELP: + return POPPLER_ANNOT_TEXT_ICON_HELP; + case EV_ANNOTATION_TEXT_ICON_NEW_PARAGRAPH: + return POPPLER_ANNOT_TEXT_ICON_NEW_PARAGRAPH; + case EV_ANNOTATION_TEXT_ICON_PARAGRAPH: + return POPPLER_ANNOT_TEXT_ICON_PARAGRAPH; + case EV_ANNOTATION_TEXT_ICON_INSERT: + return POPPLER_ANNOT_TEXT_ICON_INSERT; + case EV_ANNOTATION_TEXT_ICON_CROSS: + return POPPLER_ANNOT_TEXT_ICON_CROSS; + case EV_ANNOTATION_TEXT_ICON_CIRCLE: + return POPPLER_ANNOT_TEXT_ICON_CIRCLE; + case EV_ANNOTATION_TEXT_ICON_UNKNOWN: + default: + return POPPLER_ANNOT_TEXT_ICON_NOTE; + } +#else + return "Note"; +#endif +} + +static EvAnnotation * +ev_annot_from_poppler_annot (PopplerAnnot *poppler_annot, + EvPage *page) +{ + EvAnnotation *ev_annot = NULL; + const gchar *unimplemented_annot = NULL; + + switch (poppler_annot_get_annot_type (poppler_annot)) { + case POPPLER_ANNOT_TEXT: { + PopplerAnnotText *poppler_text; + EvAnnotationText *ev_annot_text; + + poppler_text = POPPLER_ANNOT_TEXT (poppler_annot); + + ev_annot = ev_annotation_text_new (page); + + ev_annot_text = EV_ANNOTATION_TEXT (ev_annot); + ev_annotation_text_set_is_open (ev_annot_text, + poppler_annot_text_get_is_open (poppler_text)); + ev_annotation_text_set_icon (ev_annot_text, get_annot_text_icon (poppler_text)); + } + break; + case POPPLER_ANNOT_FILE_ATTACHMENT: { + PopplerAnnotFileAttachment *poppler_annot_attachment; + EvAnnotationAttachment *ev_annot_attachment; + PopplerAttachment *poppler_attachment; + gchar *data = NULL; + gsize size; + GError *error = NULL; + + poppler_annot_attachment = POPPLER_ANNOT_FILE_ATTACHMENT (poppler_annot); + poppler_attachment = poppler_annot_file_attachment_get_attachment (poppler_annot_attachment); + + if (poppler_attachment && + attachment_save_to_buffer (poppler_attachment, &data, &size, &error)) { + EvAttachment *ev_attachment; + + ev_attachment = ev_attachment_new (poppler_attachment->name, + poppler_attachment->description, + poppler_attachment->mtime, + poppler_attachment->ctime, + size, data); + ev_annot = ev_annotation_attachment_new (page, ev_attachment); + g_object_unref (ev_attachment); + } else if (error) { + g_warning ("%s", error->message); + g_error_free (error); + } + + if (poppler_attachment) + g_object_unref (poppler_attachment); + } + break; + case POPPLER_ANNOT_LINK: + case POPPLER_ANNOT_WIDGET: + /* Ignore link and widgets annots since they are already handled */ + break; + default: { + GEnumValue *enum_value; + + enum_value = g_enum_get_value ((GEnumClass *) g_type_class_ref (POPPLER_TYPE_ANNOT_TYPE), + poppler_annot_get_annot_type (poppler_annot)); + unimplemented_annot = enum_value ? enum_value->value_name : "Unknown annotation"; + } + } + + if (unimplemented_annot) { + g_warning ("Unimplemented annotation: %s, please post a " + "bug report in Evince bugzilla " + "(http://bugzilla.mate.org) with a testcase.", + unimplemented_annot); + } + + if (ev_annot) { + time_t utime; + gchar *modified; + gchar *contents; + gchar *name; + GdkColor color; + + contents = poppler_annot_get_contents (poppler_annot); + if (contents) { + ev_annotation_set_contents (ev_annot, contents); + g_free (contents); + } + + name = poppler_annot_get_name (poppler_annot); + if (name) { + ev_annotation_set_name (ev_annot, name); + g_free (name); + } + + modified = poppler_annot_get_modified (poppler_annot); + if (poppler_date_parse (modified, &utime)) { + ev_annotation_set_modified_from_time (ev_annot, utime); + } else { + ev_annotation_set_modified (ev_annot, modified); + } + g_free (modified); + + poppler_annot_color_to_gdk_color (poppler_annot, &color); + ev_annotation_set_color (ev_annot, &color); + + if (POPPLER_IS_ANNOT_MARKUP (poppler_annot)) { + PopplerAnnotMarkup *markup; + gchar *label; + gdouble opacity; + PopplerRectangle poppler_rect; + + markup = POPPLER_ANNOT_MARKUP (poppler_annot); + + if (poppler_annot_markup_get_popup_rectangle (markup, &poppler_rect)) { + EvRectangle ev_rect; + gboolean is_open; + gdouble height; + + poppler_page_get_size (POPPLER_PAGE (page->backend_page), + NULL, &height); + ev_rect.x1 = poppler_rect.x1; + ev_rect.x2 = poppler_rect.x2; + ev_rect.y1 = height - poppler_rect.y2; + ev_rect.y2 = height - poppler_rect.y1; + + is_open = poppler_annot_markup_get_popup_is_open (markup); + + g_object_set (ev_annot, + "rectangle", &ev_rect, + "popup_is_open", is_open, + "has_popup", TRUE, + NULL); + } else { + g_object_set (ev_annot, + "has_popup", FALSE, + NULL); + } + + label = poppler_annot_markup_get_label (markup); + opacity = poppler_annot_markup_get_opacity (markup); + + g_object_set (ev_annot, + "label", label, + "opacity", opacity, + NULL); + + g_free (label); + } + } + + return ev_annot; +} + +static EvMappingList * +pdf_document_annotations_get_annotations (EvDocumentAnnotations *document_annotations, + EvPage *page) +{ + GList *retval = NULL; + PdfDocument *pdf_document; + PopplerPage *poppler_page; + EvMappingList *mapping_list; + GList *annots; + GList *list; + gdouble height; + gint i = 0; + + pdf_document = PDF_DOCUMENT (document_annotations); + poppler_page = POPPLER_PAGE (page->backend_page); + + if (pdf_document->annots) { + mapping_list = (EvMappingList *)g_hash_table_lookup (pdf_document->annots, + GINT_TO_POINTER (page->index)); + if (mapping_list) + return ev_mapping_list_ref (mapping_list); + } + + annots = poppler_page_get_annot_mapping (poppler_page); + poppler_page_get_size (poppler_page, NULL, &height); + + for (list = annots; list; list = list->next) { + PopplerAnnotMapping *mapping; + EvMapping *annot_mapping; + EvAnnotation *ev_annot; + + mapping = (PopplerAnnotMapping *)list->data; + + ev_annot = ev_annot_from_poppler_annot (mapping->annot, page); + if (!ev_annot) + continue; + + i++; + + /* Make sure annot has a unique name */ + if (!ev_annotation_get_name (ev_annot)) { + gchar *name = g_strdup_printf ("annot-%d-%d", page->index, i); + + ev_annotation_set_name (ev_annot, name); + g_free (name); + } + + annot_mapping = g_new (EvMapping, 1); + annot_mapping->area.x1 = mapping->area.x1; + annot_mapping->area.x2 = mapping->area.x2; + annot_mapping->area.y1 = height - mapping->area.y2; + annot_mapping->area.y2 = height - mapping->area.y1; + annot_mapping->data = ev_annot; + + g_object_set_data_full (G_OBJECT (ev_annot), + "poppler-annot", + g_object_ref (mapping->annot), + (GDestroyNotify) g_object_unref); + + retval = g_list_prepend (retval, annot_mapping); + } + + poppler_page_free_annot_mapping (annots); + + if (!retval) + return NULL; + + if (!pdf_document->annots) { + pdf_document->annots = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + (GDestroyNotify)NULL, + (GDestroyNotify)ev_mapping_list_unref); + } + + mapping_list = ev_mapping_list_new (page->index, g_list_reverse (retval), (GDestroyNotify)g_object_unref); + g_hash_table_insert (pdf_document->annots, + GINT_TO_POINTER (page->index), + ev_mapping_list_ref (mapping_list)); + + return mapping_list; +} + +static gboolean +pdf_document_annotations_document_is_modified (EvDocumentAnnotations *document_annotations) +{ + return PDF_DOCUMENT (document_annotations)->annots_modified; +} + +#ifdef HAVE_POPPLER_PAGE_ADD_ANNOT +static void +pdf_document_annotations_add_annotation (EvDocumentAnnotations *document_annotations, + EvAnnotation *annot, + EvRectangle *rect) +{ + PopplerAnnot *poppler_annot; + PdfDocument *pdf_document; + EvPage *page; + PopplerPage *poppler_page; + GList *list = NULL; + EvMappingList *mapping_list; + EvMapping *annot_mapping; + PopplerRectangle poppler_rect; + gdouble height; + PopplerColor poppler_color; + GdkColor color; + time_t utime; + gchar *modified; + gchar *name; + + pdf_document = PDF_DOCUMENT (document_annotations); + page = ev_annotation_get_page (annot); + poppler_page = POPPLER_PAGE (page->backend_page); + + poppler_page_get_size (poppler_page, NULL, &height); + poppler_rect.x1 = rect->x1; + poppler_rect.x2 = rect->x2; + poppler_rect.y1 = height - rect->y2; + poppler_rect.y2 = height - rect->y1; + poppler_annot = poppler_annot_text_new (pdf_document->document, &poppler_rect); + + ev_annotation_get_color (annot, &color); + poppler_color.red = color.red; + poppler_color.green = color.green; + poppler_color.blue = color.blue; + poppler_annot_set_color (poppler_annot, &poppler_color); + + if (EV_IS_ANNOTATION_MARKUP (annot)) { + EvAnnotationMarkup *markup = EV_ANNOTATION_MARKUP (annot); + const gchar *label; + + if (ev_annotation_markup_has_popup (markup)) { + EvRectangle popup_rect; + + ev_annotation_markup_get_rectangle (markup, &popup_rect); + poppler_rect.x1 = popup_rect.x1; + poppler_rect.x2 = popup_rect.x2; + poppler_rect.y1 = height - popup_rect.y2; + poppler_rect.y2 = height - popup_rect.y1; + poppler_annot_markup_set_popup (POPPLER_ANNOT_MARKUP (poppler_annot), &poppler_rect); + poppler_annot_markup_set_popup_is_open (POPPLER_ANNOT_MARKUP (poppler_annot), + ev_annotation_markup_get_popup_is_open (markup)); + } + + label = ev_annotation_markup_get_label (markup); + if (label) + poppler_annot_markup_set_label (POPPLER_ANNOT_MARKUP (poppler_annot), label); + } + + if (EV_IS_ANNOTATION_TEXT (annot)) { + EvAnnotationText *text = EV_ANNOTATION_TEXT (annot); + EvAnnotationTextIcon icon; + + icon = ev_annotation_text_get_icon (text); + poppler_annot_text_set_icon (POPPLER_ANNOT_TEXT (poppler_annot), + get_poppler_annot_text_icon (icon)); + } + poppler_page_add_annot (poppler_page, poppler_annot); + + annot_mapping = g_new (EvMapping, 1); + annot_mapping->area = *rect; + annot_mapping->data = annot; + g_object_set_data_full (G_OBJECT (annot), + "poppler-annot", + g_object_ref (poppler_annot), + (GDestroyNotify) g_object_unref); + + if (pdf_document->annots) { + mapping_list = (EvMappingList *)g_hash_table_lookup (pdf_document->annots, + GINT_TO_POINTER (page->index)); + list = ev_mapping_list_get_list (mapping_list); + name = g_strdup_printf ("annot-%d-%d", page->index, g_list_length (list) + 1); + ev_annotation_set_name (annot, name); + g_free (name); + list = g_list_append (list, annot_mapping); + } else { + pdf_document->annots = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + (GDestroyNotify)NULL, + (GDestroyNotify)ev_mapping_list_unref); + name = g_strdup_printf ("annot-%d-0", page->index); + ev_annotation_set_name (annot, name); + g_free (name); + list = g_list_append (list, annot_mapping); + mapping_list = ev_mapping_list_new (page->index, list, (GDestroyNotify)g_object_unref); + g_hash_table_insert (pdf_document->annots, + GINT_TO_POINTER (page->index), + ev_mapping_list_ref (mapping_list)); + } + + pdf_document->annots_modified = TRUE; +} +#endif /* HAVE_POPPLER_PAGE_ADD_ANNOT */ + +static void +pdf_document_annotations_save_annotation (EvDocumentAnnotations *document_annotations, + EvAnnotation *annot, + EvAnnotationsSaveMask mask) +{ + PopplerAnnot *poppler_annot; + + poppler_annot = POPPLER_ANNOT (g_object_get_data (G_OBJECT (annot), "poppler-annot")); + if (!poppler_annot) + return; + + if (mask & EV_ANNOTATIONS_SAVE_CONTENTS) + poppler_annot_set_contents (poppler_annot, + ev_annotation_get_contents (annot)); + +#ifdef HAVE_POPPLER_PAGE_ADD_ANNOT + if (mask & EV_ANNOTATIONS_SAVE_COLOR) { + PopplerColor color; + GdkColor ev_color; + + ev_annotation_get_color (annot, &ev_color); + color.red = ev_color.red; + color.green = ev_color.green; + color.blue = ev_color.blue; + poppler_annot_set_color (poppler_annot, &color); + } + + if (EV_IS_ANNOTATION_MARKUP (annot)) { + EvAnnotationMarkup *ev_markup = EV_ANNOTATION_MARKUP (annot); + PopplerAnnotMarkup *markup = POPPLER_ANNOT_MARKUP (poppler_annot); + + if (mask & EV_ANNOTATIONS_SAVE_LABEL) + poppler_annot_markup_set_label (markup, ev_annotation_markup_get_label (ev_markup)); + if (mask & EV_ANNOTATIONS_SAVE_OPACITY) + poppler_annot_markup_set_opacity (markup, ev_annotation_markup_get_opacity (ev_markup)); + if (mask & EV_ANNOTATIONS_SAVE_POPUP_IS_OPEN) + poppler_annot_markup_set_popup_is_open (markup, ev_annotation_markup_get_popup_is_open (ev_markup)); + } + + if (EV_IS_ANNOTATION_TEXT (annot)) { + EvAnnotationText *ev_text = EV_ANNOTATION_TEXT (annot); + PopplerAnnotText *text = POPPLER_ANNOT_TEXT (poppler_annot); + + if (mask & EV_ANNOTATIONS_SAVE_TEXT_IS_OPEN) { + poppler_annot_text_set_is_open (text, + ev_annotation_text_get_is_open (ev_text)); + } + if (mask & EV_ANNOTATIONS_SAVE_TEXT_ICON) { + EvAnnotationTextIcon icon; + + icon = ev_annotation_text_get_icon (ev_text); + poppler_annot_text_set_icon (text, get_poppler_annot_text_icon (icon)); + } + } +#endif /* HAVE_POPPLER_PAGE_ADD_ANNOT */ + PDF_DOCUMENT (document_annotations)->annots_modified = TRUE; +} + +static void +pdf_document_document_annotations_iface_init (EvDocumentAnnotationsInterface *iface) +{ + iface->get_annotations = pdf_document_annotations_get_annotations; + iface->document_is_modified = pdf_document_annotations_document_is_modified; +#ifdef HAVE_POPPLER_PAGE_ADD_ANNOT + iface->add_annotation = pdf_document_annotations_add_annotation; +#endif + iface->save_annotation = pdf_document_annotations_save_annotation; +} + +/* Attachments */ +struct SaveToBufferData { + gchar *buffer; + gsize len, max; +}; + +static gboolean +attachment_save_to_buffer_callback (const gchar *buf, + gsize count, + gpointer user_data, + GError **error) +{ + struct SaveToBufferData *sdata = (SaveToBufferData *)user_data; + gchar *new_buffer; + gsize new_max; + + if (sdata->len + count > sdata->max) { + new_max = MAX (sdata->max * 2, sdata->len + count); + new_buffer = (gchar *)g_realloc (sdata->buffer, new_max); + + sdata->buffer = new_buffer; + sdata->max = new_max; + } + + memcpy (sdata->buffer + sdata->len, buf, count); + sdata->len += count; + + return TRUE; +} + +static gboolean +attachment_save_to_buffer (PopplerAttachment *attachment, + gchar **buffer, + gsize *buffer_size, + GError **error) +{ + static const gint initial_max = 1024; + struct SaveToBufferData sdata; + + *buffer = NULL; + *buffer_size = 0; + + sdata.buffer = (gchar *) g_malloc (initial_max); + sdata.max = initial_max; + sdata.len = 0; + + if (! poppler_attachment_save_to_callback (attachment, + attachment_save_to_buffer_callback, + &sdata, + error)) { + g_free (sdata.buffer); + return FALSE; + } + + *buffer = sdata.buffer; + *buffer_size = sdata.len; + + return TRUE; +} + +static GList * +pdf_document_attachments_get_attachments (EvDocumentAttachments *document) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document); + GList *attachments; + GList *list; + GList *retval = NULL; + + attachments = poppler_document_get_attachments (pdf_document->document); + + for (list = attachments; list; list = list->next) { + PopplerAttachment *attachment; + EvAttachment *ev_attachment; + gchar *data = NULL; + gsize size; + GError *error = NULL; + + attachment = (PopplerAttachment *) list->data; + + if (attachment_save_to_buffer (attachment, &data, &size, &error)) { + ev_attachment = ev_attachment_new (attachment->name, + attachment->description, + attachment->mtime, + attachment->ctime, + size, data); + + retval = g_list_prepend (retval, ev_attachment); + } else { + if (error) { + g_warning ("%s", error->message); + g_error_free (error); + + g_free (data); + } + } + + g_object_unref (attachment); + } + + return g_list_reverse (retval); +} + +static gboolean +pdf_document_attachments_has_attachments (EvDocumentAttachments *document) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document); + + return poppler_document_has_attachments (pdf_document->document); +} + +static void +pdf_document_document_attachments_iface_init (EvDocumentAttachmentsInterface *iface) +{ + iface->has_attachments = pdf_document_attachments_has_attachments; + iface->get_attachments = pdf_document_attachments_get_attachments; +} + +/* Layers */ +static gboolean +pdf_document_layers_has_layers (EvDocumentLayers *document) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document); + PopplerLayersIter *iter; + + iter = poppler_layers_iter_new (pdf_document->document); + if (!iter) + return FALSE; + poppler_layers_iter_free (iter); + + return TRUE; +} + +static void +build_layers_tree (PdfDocument *pdf_document, + GtkTreeModel *model, + GtkTreeIter *parent, + PopplerLayersIter *iter) +{ + do { + GtkTreeIter tree_iter; + PopplerLayersIter *child; + PopplerLayer *layer; + EvLayer *ev_layer = NULL; + gboolean visible; + gchar *markup; + gint rb_group = 0; + + layer = poppler_layers_iter_get_layer (iter); + if (layer) { + markup = g_markup_escape_text (poppler_layer_get_title (layer), -1); + visible = poppler_layer_is_visible (layer); + rb_group = poppler_layer_get_radio_button_group_id (layer); + pdf_document->layers = g_list_append (pdf_document->layers, + g_object_ref (layer)); + ev_layer = ev_layer_new (g_list_length (pdf_document->layers) - 1, + poppler_layer_is_parent (layer), + rb_group); + } else { + gchar *title; + + title = poppler_layers_iter_get_title (iter); + markup = g_markup_escape_text (title, -1); + g_free (title); + + visible = FALSE; + layer = NULL; + } + + gtk_tree_store_append (GTK_TREE_STORE (model), &tree_iter, parent); + gtk_tree_store_set (GTK_TREE_STORE (model), &tree_iter, + EV_DOCUMENT_LAYERS_COLUMN_TITLE, markup, + EV_DOCUMENT_LAYERS_COLUMN_VISIBLE, visible, + EV_DOCUMENT_LAYERS_COLUMN_ENABLED, TRUE, /* FIXME */ + EV_DOCUMENT_LAYERS_COLUMN_SHOWTOGGLE, (layer != NULL), + EV_DOCUMENT_LAYERS_COLUMN_RBGROUP, rb_group, + EV_DOCUMENT_LAYERS_COLUMN_LAYER, ev_layer, + -1); + if (ev_layer) + g_object_unref (ev_layer); + g_free (markup); + + child = poppler_layers_iter_get_child (iter); + if (child) + build_layers_tree (pdf_document, model, &tree_iter, child); + poppler_layers_iter_free (child); + } while (poppler_layers_iter_next (iter)); +} + +static GtkTreeModel * +pdf_document_layers_get_layers (EvDocumentLayers *document) +{ + GtkTreeModel *model = NULL; + PdfDocument *pdf_document = PDF_DOCUMENT (document); + PopplerLayersIter *iter; + + iter = poppler_layers_iter_new (pdf_document->document); + if (iter) { + model = (GtkTreeModel *) gtk_tree_store_new (EV_DOCUMENT_LAYERS_N_COLUMNS, + G_TYPE_STRING, /* TITLE */ + G_TYPE_OBJECT, /* LAYER */ + G_TYPE_BOOLEAN, /* VISIBLE */ + G_TYPE_BOOLEAN, /* ENABLED */ + G_TYPE_BOOLEAN, /* SHOWTOGGLE */ + G_TYPE_INT); /* RBGROUP */ + build_layers_tree (pdf_document, model, NULL, iter); + poppler_layers_iter_free (iter); + } + return model; +} + +static void +pdf_document_layers_show_layer (EvDocumentLayers *document, + EvLayer *layer) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document); + guint layer_id = ev_layer_get_id (layer); + + poppler_layer_show (POPPLER_LAYER (g_list_nth_data (pdf_document->layers, layer_id))); +} + +static void +pdf_document_layers_hide_layer (EvDocumentLayers *document, + EvLayer *layer) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document); + guint layer_id = ev_layer_get_id (layer); + + poppler_layer_hide (POPPLER_LAYER (g_list_nth_data (pdf_document->layers, layer_id))); +} + +static gboolean +pdf_document_layers_layer_is_visible (EvDocumentLayers *document, + EvLayer *layer) +{ + PdfDocument *pdf_document = PDF_DOCUMENT (document); + guint layer_id = ev_layer_get_id (layer); + + return poppler_layer_is_visible (POPPLER_LAYER (g_list_nth_data (pdf_document->layers, layer_id))); +} + +static void +pdf_document_document_layers_iface_init (EvDocumentLayersInterface *iface) +{ + iface->has_layers = pdf_document_layers_has_layers; + iface->get_layers = pdf_document_layers_get_layers; + iface->show_layer = pdf_document_layers_show_layer; + iface->hide_layer = pdf_document_layers_hide_layer; + iface->layer_is_visible = pdf_document_layers_layer_is_visible; +} diff --git a/backend/pdf/ev-poppler.h b/backend/pdf/ev-poppler.h new file mode 100644 index 00000000..f37f2268 --- /dev/null +++ b/backend/pdf/ev-poppler.h @@ -0,0 +1,40 @@ +/* pdfdocument.h: Implementation of EvDocument for PDF + * Copyright (C) 2004, Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __PDF_DOCUMENT_H__ +#define __PDF_DOCUMENT_H__ + +#include "ev-document.h" + +G_BEGIN_DECLS + +#define PDF_TYPE_DOCUMENT (pdf_document_get_type ()) +#define PDF_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PDF_TYPE_DOCUMENT, PdfDocument)) +#define PDF_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PDF_TYPE_DOCUMENT)) + +typedef struct _PdfDocument PdfDocument; +typedef struct _PdfDocumentClass PdfDocumentClass; + +GType pdf_document_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT GType register_evince_backend (GTypeModule *module); + + +G_END_DECLS + +#endif /* __PDF_DOCUMENT_H__ */ diff --git a/backend/pdf/pdfdocument.evince-backend.in b/backend/pdf/pdfdocument.evince-backend.in new file mode 100644 index 00000000..d2b55ddc --- /dev/null +++ b/backend/pdf/pdfdocument.evince-backend.in @@ -0,0 +1,6 @@ +[Evince Backend] +Module=pdfdocument +Resident=true +_TypeDescription=PDF Documents +MimeType=application/pdf;application/x-bzpdf;application/x-gzpdf;application/x-ext-pdf + diff --git a/backend/pixbuf/Makefile.am b/backend/pixbuf/Makefile.am new file mode 100644 index 00000000..7a28e05b --- /dev/null +++ b/backend/pixbuf/Makefile.am @@ -0,0 +1,30 @@ +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/libdocument \ + -DMATELOCALEDIR=\"$(datadir)/locale\" \ + -DEVINCE_COMPILATION \ + $(BACKEND_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED) + +backend_LTLIBRARIES = libpixbufdocument.la + +libpixbufdocument_la_SOURCES = \ + pixbuf-document.c \ + pixbuf-document.h + +libpixbufdocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS) +libpixbufdocument_la_LIBADD = \ + $(top_builddir)/libdocument/libevdocument.la \ + $(BACKEND_LIBS) + +backend_in_files = pixbufdocument.evince-backend.in +backend_DATA = $(backend_in_files:.evince-backend.in=.evince-backend) + +EXTRA_DIST = $(backend_in_files) + +CLEANFILES = $(backend_DATA) + +@EV_INTLTOOL_EVINCE_BACKEND_RULE@ + +-include $(top_srcdir)/git.mk diff --git a/backend/pixbuf/pixbuf-document.c b/backend/pixbuf/pixbuf-document.c new file mode 100644 index 00000000..065fe498 --- /dev/null +++ b/backend/pixbuf/pixbuf-document.c @@ -0,0 +1,208 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* + * Copyright (C) 2004, Anders Carlsson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include "pixbuf-document.h" +#include "ev-document-thumbnails.h" +#include "ev-document-misc.h" +#include "ev-file-helpers.h" + +struct _PixbufDocumentClass +{ + EvDocumentClass parent_class; +}; + +struct _PixbufDocument +{ + EvDocument parent_instance; + + GdkPixbuf *pixbuf; + + gchar *uri; +}; + +typedef struct _PixbufDocumentClass PixbufDocumentClass; + +static void pixbuf_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); + +EV_BACKEND_REGISTER_WITH_CODE (PixbufDocument, pixbuf_document, + { + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, + pixbuf_document_document_thumbnails_iface_init) + }); + +static gboolean +pixbuf_document_load (EvDocument *document, + const char *uri, + GError **error) +{ + PixbufDocument *pixbuf_document = PIXBUF_DOCUMENT (document); + + gchar *filename; + GdkPixbuf *pixbuf; + + /* FIXME: We could actually load uris */ + filename = g_filename_from_uri (uri, NULL, error); + if (!filename) + return FALSE; + + pixbuf = gdk_pixbuf_new_from_file (filename, error); + + if (!pixbuf) + return FALSE; + + pixbuf_document->pixbuf = pixbuf; + g_free (pixbuf_document->uri); + pixbuf_document->uri = g_strdup (uri); + + return TRUE; +} + +static gboolean +pixbuf_document_save (EvDocument *document, + const char *uri, + GError **error) +{ + PixbufDocument *pixbuf_document = PIXBUF_DOCUMENT (document); + + return ev_xfer_uri_simple (pixbuf_document->uri, uri, error); +} + +static int +pixbuf_document_get_n_pages (EvDocument *document) +{ + return 1; +} + +static void +pixbuf_document_get_page_size (EvDocument *document, + EvPage *page, + double *width, + double *height) +{ + PixbufDocument *pixbuf_document = PIXBUF_DOCUMENT (document); + + *width = gdk_pixbuf_get_width (pixbuf_document->pixbuf); + *height = gdk_pixbuf_get_height (pixbuf_document->pixbuf); +} + +static cairo_surface_t * +pixbuf_document_render (EvDocument *document, + EvRenderContext *rc) +{ + PixbufDocument *pixbuf_document = PIXBUF_DOCUMENT (document); + GdkPixbuf *scaled_pixbuf, *rotated_pixbuf; + cairo_surface_t *surface; + + scaled_pixbuf = gdk_pixbuf_scale_simple ( + pixbuf_document->pixbuf, + (gdk_pixbuf_get_width (pixbuf_document->pixbuf) * rc->scale) + 0.5, + (gdk_pixbuf_get_height (pixbuf_document->pixbuf) * rc->scale) + 0.5, + GDK_INTERP_BILINEAR); + + rotated_pixbuf = gdk_pixbuf_rotate_simple (scaled_pixbuf, 360 - rc->rotation); + g_object_unref (scaled_pixbuf); + + surface = ev_document_misc_surface_from_pixbuf (rotated_pixbuf); + g_object_unref (rotated_pixbuf); + + return surface; +} + +static void +pixbuf_document_finalize (GObject *object) +{ + PixbufDocument *pixbuf_document = PIXBUF_DOCUMENT (object); + + g_object_unref (pixbuf_document->pixbuf); + g_free (pixbuf_document->uri); + + G_OBJECT_CLASS (pixbuf_document_parent_class)->finalize (object); +} + +static void +pixbuf_document_class_init (PixbufDocumentClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass); + + gobject_class->finalize = pixbuf_document_finalize; + + ev_document_class->load = pixbuf_document_load; + ev_document_class->save = pixbuf_document_save; + ev_document_class->get_n_pages = pixbuf_document_get_n_pages; + ev_document_class->get_page_size = pixbuf_document_get_page_size; + ev_document_class->render = pixbuf_document_render; +} + +static GdkPixbuf * +pixbuf_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document, + EvRenderContext *rc, + gboolean border) +{ + PixbufDocument *pixbuf_document = PIXBUF_DOCUMENT (document); + GdkPixbuf *pixbuf, *rotated_pixbuf; + gint width, height; + + width = (gint) (gdk_pixbuf_get_width (pixbuf_document->pixbuf) * rc->scale); + height = (gint) (gdk_pixbuf_get_height (pixbuf_document->pixbuf) * rc->scale); + + pixbuf = gdk_pixbuf_scale_simple (pixbuf_document->pixbuf, + width, height, + GDK_INTERP_BILINEAR); + + rotated_pixbuf = gdk_pixbuf_rotate_simple (pixbuf, 360 - rc->rotation); + g_object_unref (pixbuf); + + return rotated_pixbuf; +} + +static void +pixbuf_document_thumbnails_get_dimensions (EvDocumentThumbnails *document, + EvRenderContext *rc, + gint *width, + gint *height) +{ + PixbufDocument *pixbuf_document = PIXBUF_DOCUMENT (document); + gint p_width = gdk_pixbuf_get_width (pixbuf_document->pixbuf); + gint p_height = gdk_pixbuf_get_height (pixbuf_document->pixbuf); + + if (rc->rotation == 90 || rc->rotation == 270) { + *width = (gint) (p_height * rc->scale); + *height = (gint) (p_width * rc->scale); + } else { + *width = (gint) (p_width * rc->scale); + *height = (gint) (p_height * rc->scale); + } +} + +static void +pixbuf_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface) +{ + iface->get_thumbnail = pixbuf_document_thumbnails_get_thumbnail; + iface->get_dimensions = pixbuf_document_thumbnails_get_dimensions; +} + + +static void +pixbuf_document_init (PixbufDocument *pixbuf_document) +{ +} diff --git a/backend/pixbuf/pixbuf-document.h b/backend/pixbuf/pixbuf-document.h new file mode 100644 index 00000000..6f34372d --- /dev/null +++ b/backend/pixbuf/pixbuf-document.h @@ -0,0 +1,38 @@ +/* pdfdocument.h: Implementation of EvDocument for pixbufs + * Copyright (C) 2004, Anders Carlsson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __PIXBUF_DOCUMENT_H__ +#define __PIXBUF_DOCUMENT_H__ + +#include "ev-document.h" + +G_BEGIN_DECLS + +#define PIXBUF_TYPE_DOCUMENT (pixbuf_document_get_type ()) +#define PIXBUF_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIXBUF_TYPE_DOCUMENT, PixbufDocument)) +#define PIXBUF_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIXBUF_TYPE_DOCUMENT)) + +typedef struct _PixbufDocument PixbufDocument; + +GType pixbuf_document_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT GType register_evince_backend (GTypeModule *module); + +G_END_DECLS + +#endif /* __PIXBUF_DOCUMENT_H__ */ diff --git a/backend/pixbuf/pixbufdocument.evince-backend.in b/backend/pixbuf/pixbufdocument.evince-backend.in new file mode 100644 index 00000000..9beb526b --- /dev/null +++ b/backend/pixbuf/pixbufdocument.evince-backend.in @@ -0,0 +1,4 @@ +[Evince Backend] +Module=pixbufdocument +_TypeDescription=Images +MimeType=image/*; diff --git a/backend/ps/Makefile.am b/backend/ps/Makefile.am new file mode 100644 index 00000000..3f059de1 --- /dev/null +++ b/backend/ps/Makefile.am @@ -0,0 +1,32 @@ +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/libdocument \ + -DMATELOCALEDIR=\"$(datadir)/locale\" \ + -DEVINCE_COMPILATION \ + $(BACKEND_CFLAGS) \ + $(SPECTRE_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED) + +backend_LTLIBRARIES = libpsdocument.la + +libpsdocument_la_SOURCES = \ + ev-spectre.c \ + ev-spectre.h + +libpsdocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS) +libpsdocument_la_LIBADD = \ + $(top_builddir)/libdocument/libevdocument.la \ + $(BACKEND_LIBS) \ + $(SPECTRE_LIBS) + +backend_in_files = psdocument.evince-backend.in +backend_DATA = $(backend_in_files:.evince-backend.in=.evince-backend) + +EXTRA_DIST = $(backend_in_files) + +CLEANFILES = $(backend_DATA) + +@EV_INTLTOOL_EVINCE_BACKEND_RULE@ + +-include $(top_srcdir)/git.mk diff --git a/backend/ps/ev-spectre.c b/backend/ps/ev-spectre.c new file mode 100644 index 00000000..e1877a7b --- /dev/null +++ b/backend/ps/ev-spectre.c @@ -0,0 +1,473 @@ +/* this file is part of evince, a mate document viewer + * + * Copyright (C) 2007 Carlos Garcia Campos + * + * Evince is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Evince is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include +#include +#include + +#include "ev-spectre.h" + +#include "ev-file-exporter.h" +#include "ev-document-thumbnails.h" +#include "ev-document-misc.h" + +struct _PSDocument { + EvDocument object; + + SpectreDocument *doc; + SpectreExporter *exporter; +}; + +struct _PSDocumentClass { + EvDocumentClass parent_class; +}; + +static void ps_document_file_exporter_iface_init (EvFileExporterInterface *iface); +static void ps_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); + +EV_BACKEND_REGISTER_WITH_CODE (PSDocument, ps_document, + { + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, + ps_document_document_thumbnails_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_FILE_EXPORTER, + ps_document_file_exporter_iface_init); + }); + +/* PSDocument */ +static void +ps_document_init (PSDocument *ps_document) +{ +} + +static void +ps_document_dispose (GObject *object) +{ + PSDocument *ps = PS_DOCUMENT (object); + + if (ps->doc) { + spectre_document_free (ps->doc); + ps->doc = NULL; + } + + if (ps->exporter) { + spectre_exporter_free (ps->exporter); + ps->exporter = NULL; + } + + G_OBJECT_CLASS (ps_document_parent_class)->dispose (object); +} + +/* EvDocumentIface */ +static gboolean +ps_document_load (EvDocument *document, + const char *uri, + GError **error) +{ + PSDocument *ps = PS_DOCUMENT (document); + gchar *filename; + + filename = g_filename_from_uri (uri, NULL, error); + if (!filename) + return FALSE; + + ps->doc = spectre_document_new (); + + spectre_document_load (ps->doc, filename); + if (spectre_document_status (ps->doc)) { + gchar *filename_dsp; + + filename_dsp = g_filename_display_name (filename); + g_set_error (error, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, + _("Failed to load document ā€œ%sā€"), + filename_dsp); + g_free (filename_dsp); + g_free (filename); + + return FALSE; + } + + g_free (filename); + + return TRUE; +} + +static gboolean +ps_document_save (EvDocument *document, + const char *uri, + GError **error) +{ + PSDocument *ps = PS_DOCUMENT (document); + gchar *filename; + + filename = g_filename_from_uri (uri, NULL, error); + if (!filename) + return FALSE; + + spectre_document_save (ps->doc, filename); + if (spectre_document_status (ps->doc)) { + gchar *filename_dsp; + + filename_dsp = g_filename_display_name (filename); + g_set_error (error, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, + _("Failed to save document ā€œ%sā€"), + filename_dsp); + g_free (filename_dsp); + g_free (filename); + + return FALSE; + } + + g_free (filename); + + return TRUE; +} + +static int +ps_document_get_n_pages (EvDocument *document) +{ + PSDocument *ps = PS_DOCUMENT (document); + + return spectre_document_get_n_pages (ps->doc); +} + +static EvPage * +ps_document_get_page (EvDocument *document, + gint index) +{ + PSDocument *ps = PS_DOCUMENT (document); + SpectrePage *ps_page; + EvPage *page; + + ps_page = spectre_document_get_page (ps->doc, index); + page = ev_page_new (index); + page->backend_page = (EvBackendPage)ps_page; + page->backend_destroy_func = (EvBackendPageDestroyFunc)spectre_page_free; + + return page; +} + +static gint +get_page_rotation (SpectrePage *page) +{ + switch (spectre_page_get_orientation (page)) { + default: + case SPECTRE_ORIENTATION_PORTRAIT: + return 0; + case SPECTRE_ORIENTATION_LANDSCAPE: + return 90; + case SPECTRE_ORIENTATION_REVERSE_PORTRAIT: + return 180; + case SPECTRE_ORIENTATION_REVERSE_LANDSCAPE: + return 270; + } + + return 0; +} + +static void +ps_document_get_page_size (EvDocument *document, + EvPage *page, + double *width, + double *height) +{ + SpectrePage *ps_page; + gdouble page_width, page_height; + gint pwidth, pheight; + gint rotate; + + ps_page = (SpectrePage *)page->backend_page; + + spectre_page_get_size (ps_page, &pwidth, &pheight); + + rotate = get_page_rotation (ps_page); + if (rotate == 90 || rotate == 270) { + page_height = pwidth; + page_width = pheight; + } else { + page_width = pwidth; + page_height = pheight; + } + + if (width) { + *width = page_width; + } + + if (height) { + *height = page_height; + } +} + +static char * +ps_document_get_page_label (EvDocument *document, + EvPage *page) +{ + return g_strdup (spectre_page_get_label ((SpectrePage *)page->backend_page)); +} + +static EvDocumentInfo * +ps_document_get_info (EvDocument *document) +{ + PSDocument *ps = PS_DOCUMENT (document); + EvDocumentInfo *info; + const gchar *creator; + SpectrePage *ps_page; + gint width, height; + + info = g_new0 (EvDocumentInfo, 1); + info->fields_mask = EV_DOCUMENT_INFO_TITLE | + EV_DOCUMENT_INFO_FORMAT | + EV_DOCUMENT_INFO_CREATOR | + EV_DOCUMENT_INFO_N_PAGES | + EV_DOCUMENT_INFO_PAPER_SIZE; + + creator = spectre_document_get_creator (ps->doc); + + ps_page = spectre_document_get_page (ps->doc, 0); + spectre_page_get_size (ps_page, &width, &height); + spectre_page_free (ps_page); + + info->title = g_strdup (spectre_document_get_title (ps->doc)); + info->format = g_strdup (spectre_document_get_format (ps->doc)); + info->creator = g_strdup (creator ? creator : spectre_document_get_for (ps->doc)); + info->n_pages = spectre_document_get_n_pages (ps->doc); + info->paper_width = width / 72.0f * 25.4f; + info->paper_height = height / 72.0f * 25.4f; + + return info; +} + +static gboolean +ps_document_get_backend_info (EvDocument *document, + EvDocumentBackendInfo *info) +{ + info->name = "libspectre"; + info->version = SPECTRE_VERSION_STRING; + + return TRUE; +} + +static cairo_surface_t * +ps_document_render (EvDocument *document, + EvRenderContext *rc) +{ + SpectrePage *ps_page; + SpectreRenderContext *src; + gint width_points; + gint height_points; + gint width, height; + gint swidth, sheight; + guchar *data = NULL; + gint stride; + gint rotation; + cairo_surface_t *surface; + static const cairo_user_data_key_t key; + + ps_page = (SpectrePage *)rc->page->backend_page; + + spectre_page_get_size (ps_page, &width_points, &height_points); + + width = (gint) ((width_points * rc->scale) + 0.5); + height = (gint) ((height_points * rc->scale) + 0.5); + rotation = (rc->rotation + get_page_rotation (ps_page)) % 360; + + src = spectre_render_context_new (); + spectre_render_context_set_scale (src, + (gdouble)width / width_points, + (gdouble)height / height_points); + spectre_render_context_set_rotation (src, rotation); + spectre_page_render (ps_page, src, &data, &stride); + spectre_render_context_free (src); + + if (!data) { + return NULL; + } + + if (spectre_page_status (ps_page)) { + g_warning ("%s", spectre_status_to_string (spectre_page_status (ps_page))); + g_free (data); + + return NULL; + } + + if (rotation == 90 || rotation == 270) { + swidth = height; + sheight = width; + } else { + swidth = width; + sheight = height; + } + + surface = cairo_image_surface_create_for_data (data, + CAIRO_FORMAT_RGB24, + swidth, sheight, + stride); + cairo_surface_set_user_data (surface, &key, + data, (cairo_destroy_func_t)g_free); + return surface; +} + +static void +ps_document_class_init (PSDocumentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass); + + object_class->dispose = ps_document_dispose; + + ev_document_class->load = ps_document_load; + ev_document_class->save = ps_document_save; + ev_document_class->get_n_pages = ps_document_get_n_pages; + ev_document_class->get_page = ps_document_get_page; + ev_document_class->get_page_size = ps_document_get_page_size; + ev_document_class->get_page_label = ps_document_get_page_label; + ev_document_class->get_info = ps_document_get_info; + ev_document_class->get_backend_info = ps_document_get_backend_info; + ev_document_class->render = ps_document_render; +} + +/* EvDocumentThumbnailsIface */ +static GdkPixbuf * +ps_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document_thumbnails, + EvRenderContext *rc, + gboolean border) +{ + PSDocument *ps = PS_DOCUMENT (document_thumbnails); + cairo_surface_t *surface; + GdkPixbuf *pixbuf = NULL; + + surface = ps_document_render (EV_DOCUMENT (ps), rc); + if (!surface) { + g_warning ("Error rendering thumbnail"); + return NULL; + } + + pixbuf = ev_document_misc_pixbuf_from_surface (surface); + cairo_surface_destroy (surface); + + if (border) { + GdkPixbuf *border_pixbuf; + + border_pixbuf = ev_document_misc_get_thumbnail_frame (-1, -1, pixbuf); + g_object_unref (pixbuf); + pixbuf = border_pixbuf; + } + + return pixbuf; +} + +static void +ps_document_thumbnails_get_dimensions (EvDocumentThumbnails *document_thumbnails, + EvRenderContext *rc, + gint *width, + gint *height) +{ + PSDocument *ps = PS_DOCUMENT (document_thumbnails); + gdouble page_width, page_height; + + ps_document_get_page_size (EV_DOCUMENT (ps), + rc->page, + &page_width, &page_height); + + if (rc->rotation == 90 || rc->rotation == 270) { + *width = (gint) (page_height * rc->scale); + *height = (gint) (page_width * rc->scale); + } else { + *width = (gint) (page_width * rc->scale); + *height = (gint) (page_height * rc->scale); + } +} + +static void +ps_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface) +{ + iface->get_thumbnail = ps_document_thumbnails_get_thumbnail; + iface->get_dimensions = ps_document_thumbnails_get_dimensions; +} + +/* EvFileExporterIface */ +static void +ps_document_file_exporter_begin (EvFileExporter *exporter, + EvFileExporterContext *fc) +{ + PSDocument *ps = PS_DOCUMENT (exporter); + + if (ps->exporter) + spectre_exporter_free (ps->exporter); + + switch (fc->format) { + case EV_FILE_FORMAT_PS: + ps->exporter = + spectre_exporter_new (ps->doc, + SPECTRE_EXPORTER_FORMAT_PS); + break; + case EV_FILE_FORMAT_PDF: + ps->exporter = + spectre_exporter_new (ps->doc, + SPECTRE_EXPORTER_FORMAT_PDF); + break; + default: + g_assert_not_reached (); + } + + spectre_exporter_begin (ps->exporter, fc->filename); +} + +static void +ps_document_file_exporter_do_page (EvFileExporter *exporter, + EvRenderContext *rc) +{ + PSDocument *ps = PS_DOCUMENT (exporter); + + spectre_exporter_do_page (ps->exporter, rc->page->index); +} + +static void +ps_document_file_exporter_end (EvFileExporter *exporter) +{ + PSDocument *ps = PS_DOCUMENT (exporter); + + spectre_exporter_end (ps->exporter); +} + +static EvFileExporterCapabilities +ps_document_file_exporter_get_capabilities (EvFileExporter *exporter) +{ + return EV_FILE_EXPORTER_CAN_PAGE_SET | + EV_FILE_EXPORTER_CAN_COPIES | + EV_FILE_EXPORTER_CAN_COLLATE | + EV_FILE_EXPORTER_CAN_REVERSE | + EV_FILE_EXPORTER_CAN_GENERATE_PS | + EV_FILE_EXPORTER_CAN_GENERATE_PDF; +} + +static void +ps_document_file_exporter_iface_init (EvFileExporterInterface *iface) +{ + iface->begin = ps_document_file_exporter_begin; + iface->do_page = ps_document_file_exporter_do_page; + iface->end = ps_document_file_exporter_end; + iface->get_capabilities = ps_document_file_exporter_get_capabilities; +} diff --git a/backend/ps/ev-spectre.h b/backend/ps/ev-spectre.h new file mode 100644 index 00000000..17b18db0 --- /dev/null +++ b/backend/ps/ev-spectre.h @@ -0,0 +1,48 @@ +/* + * Ghostscript widget for GTK/MATE + * + * Copyright 1998 - 2005 The Free Software Foundation + * + * Authors: Jaka Mocnik, Federico Mena (Quartic), Szekeres Istvan (Pista) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PS_DOCUMENT_H__ +#define __PS_DOCUMENT_H__ + +#include + +#include "ev-document.h" + +G_BEGIN_DECLS + +#define PS_TYPE_DOCUMENT (ps_document_get_type()) +#define PS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PS_TYPE_DOCUMENT, PSDocument)) +#define PS_DOCUMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PS_TYPE_DOCUMENT, PSDocumentClass)) +#define PS_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PS_TYPE_DOCUMENT)) +#define PS_DOCUMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PS_TYPE_DOCUMENT, PSDocumentClass)) + +typedef struct _PSDocument PSDocument; +typedef struct _PSDocumentClass PSDocumentClass; + +GType ps_document_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT GType register_evince_backend (GTypeModule *module); + +G_END_DECLS + +#endif /* __PS_DOCUMENT_H__ */ diff --git a/backend/ps/psdocument.evince-backend.in b/backend/ps/psdocument.evince-backend.in new file mode 100644 index 00000000..af24be61 --- /dev/null +++ b/backend/ps/psdocument.evince-backend.in @@ -0,0 +1,5 @@ +[Evince Backend] +Module=psdocument +Resident=true +_TypeDescription=PostScript Documents +MimeType=application/postscript;application/x-bzpostscript;application/x-gzpostscript;image/x-eps;image/x-bzeps;image/x-gzeps diff --git a/backend/tiff/Makefile.am b/backend/tiff/Makefile.am new file mode 100644 index 00000000..9f1a00e6 --- /dev/null +++ b/backend/tiff/Makefile.am @@ -0,0 +1,33 @@ +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/libdocument \ + -DMATELOCALEDIR=\"$(datadir)/locale\" \ + -DEVINCE_COMPILATION \ + $(BACKEND_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED) + +backend_LTLIBRARIES = libtiffdocument.la + +libtiffdocument_la_SOURCES = \ + tiff-document.c \ + tiff-document.h \ + tiff2ps.c \ + tiff2ps.h + +libtiffdocument_la_LDFLAGS = $(BACKEND_LIBTOOL_FLAGS) +libtiffdocument_la_LIBADD = \ + $(top_builddir)/libdocument/libevdocument.la \ + $(BACKEND_LIBS) \ + -ltiff + +backend_in_files = tiffdocument.evince-backend.in +backend_DATA = $(backend_in_files:.evince-backend.in=.evince-backend) + +EXTRA_DIST = $(backend_in_files) + +CLEANFILES = $(backend_DATA) + +@EV_INTLTOOL_EVINCE_BACKEND_RULE@ + +-include $(top_srcdir)/git.mk diff --git a/backend/tiff/tiff-document.c b/backend/tiff/tiff-document.c new file mode 100644 index 00000000..9c113b49 --- /dev/null +++ b/backend/tiff/tiff-document.c @@ -0,0 +1,533 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* + * Copyright (C) 2005, Jonathan Blandford + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* FIXME: Should probably buffer calls to libtiff with TIFFSetWarningHandler + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "tiffio.h" +#include "tiff2ps.h" +#include "tiff-document.h" +#include "ev-document-misc.h" +#include "ev-document-thumbnails.h" +#include "ev-file-exporter.h" +#include "ev-file-helpers.h" + +struct _TiffDocumentClass +{ + EvDocumentClass parent_class; +}; + +struct _TiffDocument +{ + EvDocument parent_instance; + + TIFF *tiff; + gint n_pages; + TIFF2PSContext *ps_export_ctx; + + gchar *uri; +}; + +typedef struct _TiffDocumentClass TiffDocumentClass; + +static void tiff_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface); +static void tiff_document_document_file_exporter_iface_init (EvFileExporterInterface *iface); + +EV_BACKEND_REGISTER_WITH_CODE (TiffDocument, tiff_document, + { + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, + tiff_document_document_thumbnails_iface_init); + EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_FILE_EXPORTER, + tiff_document_document_file_exporter_iface_init); + }); + +static TIFFErrorHandler orig_error_handler = NULL; +static TIFFErrorHandler orig_warning_handler = NULL; + +static void +push_handlers (void) +{ + orig_error_handler = TIFFSetErrorHandler (NULL); + orig_warning_handler = TIFFSetWarningHandler (NULL); +} + +static void +pop_handlers (void) +{ + TIFFSetErrorHandler (orig_error_handler); + TIFFSetWarningHandler (orig_warning_handler); +} + +static gboolean +tiff_document_load (EvDocument *document, + const char *uri, + GError **error) +{ + TiffDocument *tiff_document = TIFF_DOCUMENT (document); + gchar *filename; + TIFF *tiff; + + filename = g_filename_from_uri (uri, NULL, error); + if (!filename) + return FALSE; + + push_handlers (); + tiff = TIFFOpen (filename, "r"); + if (tiff) { + guint32 w, h; + + /* FIXME: unused data? why bother here */ + TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &w); + TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &h); + } + + if (!tiff) { + pop_handlers (); + + g_set_error_literal (error, + EV_DOCUMENT_ERROR, + EV_DOCUMENT_ERROR_INVALID, + _("Invalid document")); + + g_free (filename); + return FALSE; + } + + tiff_document->tiff = tiff; + g_free (tiff_document->uri); + g_free (filename); + tiff_document->uri = g_strdup (uri); + + pop_handlers (); + return TRUE; +} + +static gboolean +tiff_document_save (EvDocument *document, + const char *uri, + GError **error) +{ + TiffDocument *tiff_document = TIFF_DOCUMENT (document); + + return ev_xfer_uri_simple (tiff_document->uri, uri, error); +} + +static int +tiff_document_get_n_pages (EvDocument *document) +{ + TiffDocument *tiff_document = TIFF_DOCUMENT (document); + + g_return_val_if_fail (TIFF_IS_DOCUMENT (document), 0); + g_return_val_if_fail (tiff_document->tiff != NULL, 0); + + if (tiff_document->n_pages == -1) { + push_handlers (); + tiff_document->n_pages = 0; + + do { + tiff_document->n_pages ++; + } + while (TIFFReadDirectory (tiff_document->tiff)); + pop_handlers (); + } + + return tiff_document->n_pages; +} + +static void +tiff_document_get_resolution (TiffDocument *tiff_document, + gfloat *x_res, + gfloat *y_res) +{ + gfloat x = 72.0, y = 72.0; + gushort unit; + + if (TIFFGetField (tiff_document->tiff, TIFFTAG_XRESOLUTION, &x) && + TIFFGetField (tiff_document->tiff, TIFFTAG_YRESOLUTION, &y)) { + if (TIFFGetFieldDefaulted (tiff_document->tiff, TIFFTAG_RESOLUTIONUNIT, &unit)) { + if (unit == RESUNIT_CENTIMETER) { + x *= 2.54; + y *= 2.54; + } + } + } + + *x_res = x; + *y_res = y; +} + +static void +tiff_document_get_page_size (EvDocument *document, + EvPage *page, + double *width, + double *height) +{ + guint32 w, h; + gfloat x_res, y_res; + TiffDocument *tiff_document = TIFF_DOCUMENT (document); + + g_return_if_fail (TIFF_IS_DOCUMENT (document)); + g_return_if_fail (tiff_document->tiff != NULL); + + push_handlers (); + if (TIFFSetDirectory (tiff_document->tiff, page->index) != 1) { + pop_handlers (); + return; + } + + TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGEWIDTH, &w); + TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGELENGTH, &h); + tiff_document_get_resolution (tiff_document, &x_res, &y_res); + h = h * (x_res / y_res); + + *width = w; + *height = h; + + pop_handlers (); +} + +static cairo_surface_t * +tiff_document_render (EvDocument *document, + EvRenderContext *rc) +{ + TiffDocument *tiff_document = TIFF_DOCUMENT (document); + int width, height; + float x_res, y_res; + gint rowstride, bytes; + guchar *pixels = NULL; + guchar *p; + int orientation; + cairo_surface_t *surface; + cairo_surface_t *rotated_surface; + static const cairo_user_data_key_t key; + + g_return_val_if_fail (TIFF_IS_DOCUMENT (document), NULL); + g_return_val_if_fail (tiff_document->tiff != NULL, NULL); + + push_handlers (); + if (TIFFSetDirectory (tiff_document->tiff, rc->page->index) != 1) { + pop_handlers (); + return NULL; + } + + if (!TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGEWIDTH, &width)) { + pop_handlers (); + return NULL; + } + + if (! TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGELENGTH, &height)) { + pop_handlers (); + return NULL; + } + + if (! TIFFGetField (tiff_document->tiff, TIFFTAG_ORIENTATION, &orientation)) { + orientation = ORIENTATION_TOPLEFT; + } + + tiff_document_get_resolution (tiff_document, &x_res, &y_res); + + pop_handlers (); + + /* Sanity check the doc */ + if (width <= 0 || height <= 0) + return NULL; + +#ifdef HAVE_CAIRO_FORMAT_STRIDE_FOR_WIDTH + rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width); +#else + rowstride = width * 4; +#endif + if (rowstride / 4 != width) + /* overflow, or cairo was changed in an unsupported way */ + return NULL; + + bytes = height * rowstride; + if (bytes / rowstride != height) + /* overflow */ + return NULL; + + pixels = g_try_malloc (bytes); + if (!pixels) + return NULL; + + surface = cairo_image_surface_create_for_data (pixels, + CAIRO_FORMAT_RGB24, + width, height, + rowstride); + cairo_surface_set_user_data (surface, &key, + pixels, (cairo_destroy_func_t)g_free); + + TIFFReadRGBAImageOriented (tiff_document->tiff, + width, height, + (uint32 *)pixels, + orientation, 1); + pop_handlers (); + + /* Convert the format returned by libtiff to + * what cairo expects + */ + p = pixels; + while (p < pixels + bytes) { + guint32 *pixel = (guint32*)p; + guint8 r = TIFFGetR(*pixel); + guint8 g = TIFFGetG(*pixel); + guint8 b = TIFFGetB(*pixel); + guint8 a = TIFFGetA(*pixel); + + *pixel = (a << 24) | (r << 16) | (g << 8) | b; + + p += 4; + } + + rotated_surface = ev_document_misc_surface_rotate_and_scale (surface, + (width * rc->scale) + 0.5, + (height * rc->scale * (x_res / y_res)) + 0.5, + rc->rotation); + cairo_surface_destroy (surface); + + return rotated_surface; +} + +static GdkPixbuf * +tiff_document_render_pixbuf (EvDocument *document, + EvRenderContext *rc) +{ + TiffDocument *tiff_document = TIFF_DOCUMENT (document); + int width, height; + float x_res, y_res; + gint rowstride, bytes; + guchar *pixels = NULL; + GdkPixbuf *pixbuf; + GdkPixbuf *scaled_pixbuf; + GdkPixbuf *rotated_pixbuf; + + push_handlers (); + if (TIFFSetDirectory (tiff_document->tiff, rc->page->index) != 1) { + pop_handlers (); + return NULL; + } + + if (!TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGEWIDTH, &width)) { + pop_handlers (); + return NULL; + } + + if (! TIFFGetField (tiff_document->tiff, TIFFTAG_IMAGELENGTH, &height)) { + pop_handlers (); + return NULL; + } + + tiff_document_get_resolution (tiff_document, &x_res, &y_res); + + pop_handlers (); + + /* Sanity check the doc */ + if (width <= 0 || height <= 0) + return NULL; + + rowstride = width * 4; + if (rowstride / 4 != width) + /* overflow */ + return NULL; + + bytes = height * rowstride; + if (bytes / rowstride != height) + /* overflow */ + return NULL; + + pixels = g_try_malloc (bytes); + if (!pixels) + return NULL; + + pixbuf = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8, + width, height, rowstride, + (GdkPixbufDestroyNotify) g_free, NULL); + TIFFReadRGBAImageOriented (tiff_document->tiff, + width, height, + (uint32 *)pixels, + ORIENTATION_TOPLEFT, 1); + pop_handlers (); + + scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf, + width * rc->scale, + height * rc->scale * (x_res / y_res), + GDK_INTERP_BILINEAR); + g_object_unref (pixbuf); + + rotated_pixbuf = gdk_pixbuf_rotate_simple (scaled_pixbuf, 360 - rc->rotation); + g_object_unref (scaled_pixbuf); + + return rotated_pixbuf; +} + +static gchar * +tiff_document_get_page_label (EvDocument *document, + EvPage *page) +{ + TiffDocument *tiff_document = TIFF_DOCUMENT (document); + static gchar *label; + + if (TIFFGetField (tiff_document->tiff, TIFFTAG_PAGENAME, &label) && + g_utf8_validate (label, -1, NULL)) { + return g_strdup (label); + } + + return NULL; +} + +static void +tiff_document_finalize (GObject *object) +{ + TiffDocument *tiff_document = TIFF_DOCUMENT (object); + + if (tiff_document->tiff) + TIFFClose (tiff_document->tiff); + if (tiff_document->uri) + g_free (tiff_document->uri); + + G_OBJECT_CLASS (tiff_document_parent_class)->finalize (object); +} + +static void +tiff_document_class_init (TiffDocumentClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass); + + gobject_class->finalize = tiff_document_finalize; + + ev_document_class->load = tiff_document_load; + ev_document_class->save = tiff_document_save; + ev_document_class->get_n_pages = tiff_document_get_n_pages; + ev_document_class->get_page_size = tiff_document_get_page_size; + ev_document_class->render = tiff_document_render; + ev_document_class->get_page_label = tiff_document_get_page_label; +} + +static GdkPixbuf * +tiff_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document, + EvRenderContext *rc, + gboolean border) +{ + GdkPixbuf *pixbuf; + + pixbuf = tiff_document_render_pixbuf (EV_DOCUMENT (document), rc); + + if (border) { + GdkPixbuf *tmp_pixbuf = pixbuf; + + pixbuf = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf); + g_object_unref (tmp_pixbuf); + } + + return pixbuf; +} + +static void +tiff_document_thumbnails_get_dimensions (EvDocumentThumbnails *document, + EvRenderContext *rc, + gint *width, + gint *height) +{ + gdouble page_width, page_height; + + tiff_document_get_page_size (EV_DOCUMENT (document), + rc->page, + &page_width, &page_height); + + if (rc->rotation == 90 || rc->rotation == 270) { + *width = (gint) (page_height * rc->scale); + *height = (gint) (page_width * rc->scale); + } else { + *width = (gint) (page_width * rc->scale); + *height = (gint) (page_height * rc->scale); + } +} + +static void +tiff_document_document_thumbnails_iface_init (EvDocumentThumbnailsInterface *iface) +{ + iface->get_thumbnail = tiff_document_thumbnails_get_thumbnail; + iface->get_dimensions = tiff_document_thumbnails_get_dimensions; +} + +/* postscript exporter implementation */ +static void +tiff_document_file_exporter_begin (EvFileExporter *exporter, + EvFileExporterContext *fc) +{ + TiffDocument *document = TIFF_DOCUMENT (exporter); + + document->ps_export_ctx = tiff2ps_context_new(fc->filename); +} + +static void +tiff_document_file_exporter_do_page (EvFileExporter *exporter, EvRenderContext *rc) +{ + TiffDocument *document = TIFF_DOCUMENT (exporter); + + if (document->ps_export_ctx == NULL) + return; + if (TIFFSetDirectory (document->tiff, rc->page->index) != 1) + return; + tiff2ps_process_page (document->ps_export_ctx, document->tiff, + 0, 0, 0, 0, 0); +} + +static void +tiff_document_file_exporter_end (EvFileExporter *exporter) +{ + TiffDocument *document = TIFF_DOCUMENT (exporter); + + if (document->ps_export_ctx == NULL) + return; + tiff2ps_context_finalize(document->ps_export_ctx); +} + +static EvFileExporterCapabilities +tiff_document_file_exporter_get_capabilities (EvFileExporter *exporter) +{ + return EV_FILE_EXPORTER_CAN_PAGE_SET | + EV_FILE_EXPORTER_CAN_COPIES | + EV_FILE_EXPORTER_CAN_COLLATE | + EV_FILE_EXPORTER_CAN_REVERSE | + EV_FILE_EXPORTER_CAN_GENERATE_PS; +} + +static void +tiff_document_document_file_exporter_iface_init (EvFileExporterInterface *iface) +{ + iface->begin = tiff_document_file_exporter_begin; + iface->do_page = tiff_document_file_exporter_do_page; + iface->end = tiff_document_file_exporter_end; + iface->get_capabilities = tiff_document_file_exporter_get_capabilities; +} + +static void +tiff_document_init (TiffDocument *tiff_document) +{ + tiff_document->n_pages = -1; +} diff --git a/backend/tiff/tiff-document.h b/backend/tiff/tiff-document.h new file mode 100644 index 00000000..a188f143 --- /dev/null +++ b/backend/tiff/tiff-document.h @@ -0,0 +1,38 @@ + +/* pdfdocument.h: Implementation of EvDocument for tiffs + * Copyright (C) 2005, Jonathan Blandford + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __TIFF_DOCUMENT_H__ +#define __TIFF_DOCUMENT_H__ + +#include "ev-document.h" + +G_BEGIN_DECLS + +#define TIFF_TYPE_DOCUMENT (tiff_document_get_type ()) +#define TIFF_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TIFF_TYPE_DOCUMENT, TiffDocument)) +#define TIFF_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TIFF_TYPE_DOCUMENT)) + +typedef struct _TiffDocument TiffDocument; + +GType tiff_document_get_type (void) G_GNUC_CONST; +G_MODULE_EXPORT GType register_evince_backend (GTypeModule *module); + +G_END_DECLS + +#endif /* __TIFF_DOCUMENT_H__ */ diff --git a/backend/tiff/tiff2ps.c b/backend/tiff/tiff2ps.c new file mode 100644 index 00000000..43a6cb32 --- /dev/null +++ b/backend/tiff/tiff2ps.c @@ -0,0 +1,1872 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */ +/* $Id$ */ + +/* + * Copyright (c) 1988-1997 Sam Leffler + * Copyright (c) 1991-1997 Silicon Graphics, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that (i) the above copyright notices and this permission notice appear in + * all copies of the software and related documentation, and (ii) the names of + * Sam Leffler and Silicon Graphics may not be used in any advertising or + * publicity relating to the software without the specific, prior written + * permission of Sam Leffler and Silicon Graphics. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR + * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, + * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF + * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +/* + * Modified for use as Evince TIFF ps exporter by + * Matthew S. Wilson + * Modifications Copyright (C) 2005 rpath, Inc. + * + */ + +#include +#include +#include /* for atof */ +#include +#include +#include +#include + +#include +#include + +#include "tiff2ps.h" + +/* + * Revision history + * + * 2001-Mar-21 + * I (Bruce A. Mallett) added this revision history comment ;) + * + * Fixed PS_Lvl2page() code which outputs non-ASCII85 raw + * data. Moved test for when to output a line break to + * *after* the output of a character. This just serves + * to fix an eye-nuisance where the first line of raw + * data was one character shorter than subsequent lines. + * + * Added an experimental ASCII85 encoder which can be used + * only when there is a single buffer of bytes to be encoded. + * This version is much faster at encoding a straight-line + * buffer of data because it can avoid alot of the loop + * overhead of the byte-by-bye version. To use this version + * you need to define EXP_ASCII85ENCODER (experimental ...). + * + * Added bug fix given by Michael Schmidt to PS_Lvl2page() + * in which an end-of-data marker ('>') was not being output + * when producing non-ASCII85 encoded PostScript Level 2 + * data. + * + * Fixed PS_Lvl2colorspace() so that it no longer assumes that + * a TIFF having more than 2 planes is a CMYK. This routine + * no longer looks at the samples per pixel but instead looks + * at the "photometric" value. This change allows support of + * CMYK TIFFs. + * + * Modified the PostScript L2 imaging loop so as to test if + * the input stream is still open before attempting to do a + * flushfile on it. This was done because some RIPs close + * the stream after doing the image operation. + * + * Got rid of the realloc() being done inside a loop in the + * PSRawDataBW() routine. The code now walks through the + * byte-size array outside the loop to determine the largest + * size memory block that will be needed. + * + * Added "-m" switch to ask tiff2ps to, where possible, use the + * "imagemask" operator instead of the "image" operator. + * + * Added the "-i #" switch to allow interpolation to be disabled. + * + * Unrolled a loop or two to improve performance. + */ + +/* + * Define EXP_ASCII85ENCODER if you want to use an experimental + * version of the ASCII85 encoding routine. The advantage of + * using this routine is that tiff2ps will convert to ASCII85 + * encoding at between 3 and 4 times the speed as compared to + * using the old (non-experimental) encoder. The disadvantage + * is that you will be using a new (and unproven) encoding + * routine. So user beware, you have been warned! + */ + +#define EXP_ASCII85ENCODER + +/* + * NB: this code assumes uint32 works with printf's %l[ud]. + */ + +struct _TIFF2PSContext +{ + char *filename; /* input filename */ + FILE *fd; /* output file stream */ + int ascii85; /* use ASCII85 encoding */ + int interpolate; /* interpolate level2 image */ + int level2; /* generate PostScript level 2 */ + int level3; /* generate PostScript level 3 */ + int generateEPSF; /* generate Encapsulated PostScript */ + int PSduplex; /* enable duplex printing */ + int PStumble; /* enable top edge binding */ + int PSavoiddeadzone; /* enable avoiding printer deadzone */ + double maxPageHeight; /* maximum size to fit on page */ + double splitOverlap; /* amount for split pages to overlag */ + int rotate; /* rotate image by 180 degrees */ + int useImagemask; /* Use imagemask instead of image operator */ + uint16 res_unit; /* Resolution units: 2 - inches, 3 - cm */ + int npages; /* number of pages processed */ + + tsize_t tf_bytesperrow; + tsize_t ps_bytesperrow; + tsize_t tf_rowsperstrip; + tsize_t tf_numberstrips; + + /* + * ASCII85 Encoding Support. + */ + unsigned char ascii85buf[10]; + int ascii85count; + int ascii85breaklen; + uint16 samplesperpixel; + uint16 bitspersample; + uint16 planarconfiguration; + uint16 photometric; + uint16 compression; + uint16 extrasamples; + int alpha; +}; + +static void PSpage(TIFF2PSContext*, TIFF*, uint32, uint32); +static void PSColorContigPreamble(TIFF2PSContext*, uint32, uint32, int); +static void PSColorSeparatePreamble(TIFF2PSContext*, uint32, uint32, int); +static void PSDataColorContig(TIFF2PSContext*, TIFF*, uint32, uint32, int); +static void PSDataColorSeparate(TIFF2PSContext*, TIFF*, uint32, uint32, int); +static void PSDataPalette(TIFF2PSContext*, TIFF*, uint32, uint32); +static void PSDataBW(TIFF2PSContext*, TIFF*, uint32, uint32); +static void Ascii85Init(TIFF2PSContext*); +static void Ascii85Put(TIFF2PSContext*, unsigned char); +static void Ascii85Flush(TIFF2PSContext*); +static void PSHead(TIFF2PSContext*, TIFF*, uint32, uint32, + double, double, double, double); +static void PSTail(TIFF2PSContext*); + +#if defined( EXP_ASCII85ENCODER ) +static int Ascii85EncodeBlock(TIFF2PSContext*, uint8 * ascii85_p, + unsigned f_eod, const uint8 * raw_p, int raw_l); +#endif + +TIFF2PSContext* tiff2ps_context_new(const gchar *filename) { + TIFF2PSContext* ctx; + + ctx = g_new0(TIFF2PSContext, 1); + ctx->filename = g_strdup(filename); + ctx->fd = g_fopen(ctx->filename, "w"); + if (ctx->fd == NULL) { + g_free (ctx->filename); + g_free (ctx); + return NULL; + } + ctx->interpolate = TRUE; /* interpolate level2 image */ + ctx->PSavoiddeadzone = TRUE; /* enable avoiding printer deadzone */ + return ctx; +} + +void tiff2ps_context_finalize(TIFF2PSContext *ctx) { + PSTail(ctx); + fclose(ctx->fd); + g_free(ctx->filename); + g_free(ctx); +} + +static int +checkImage(TIFF2PSContext *ctx, TIFF* tif) +{ + switch (ctx->photometric) { + case PHOTOMETRIC_YCBCR: + if ((ctx->compression == COMPRESSION_JPEG + || ctx->compression == COMPRESSION_OJPEG) + && ctx->planarconfiguration == PLANARCONFIG_CONTIG) { + /* can rely on libjpeg to convert to RGB */ + TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, + JPEGCOLORMODE_RGB); + ctx->photometric = PHOTOMETRIC_RGB; + } else { + if (ctx->level2 || ctx->level3) + break; + TIFFError(ctx->filename, "Can not handle image with %s", + "Ctx->PhotometricInterpretation=YCbCr"); + return (0); + } + /* fall thru... */ + case PHOTOMETRIC_RGB: + if (ctx->alpha && ctx->bitspersample != 8) { + TIFFError(ctx->filename, + "Can not handle %d-bit/sample RGB image with ctx->alpha", + ctx->bitspersample); + return (0); + } + /* fall thru... */ + case PHOTOMETRIC_SEPARATED: + case PHOTOMETRIC_PALETTE: + case PHOTOMETRIC_MINISBLACK: + case PHOTOMETRIC_MINISWHITE: + break; + case PHOTOMETRIC_LOGL: + case PHOTOMETRIC_LOGLUV: + if (ctx->compression != COMPRESSION_SGILOG && + ctx->compression != COMPRESSION_SGILOG24) { + TIFFError(ctx->filename, + "Can not handle %s data with ctx->compression other than SGILog", + (ctx->photometric == PHOTOMETRIC_LOGL) ? + "LogL" : "LogLuv" + ); + return (0); + } + /* rely on library to convert to RGB/greyscale */ + TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_8BIT); + ctx->photometric = (ctx->photometric == PHOTOMETRIC_LOGL) ? + PHOTOMETRIC_MINISBLACK : PHOTOMETRIC_RGB; + ctx->bitspersample = 8; + break; + case PHOTOMETRIC_CIELAB: + /* fall thru... */ + default: + TIFFError(ctx->filename, + "Can not handle image with Ctx->PhotometricInterpretation=%d", + ctx->photometric); + return (0); + } + switch (ctx->bitspersample) { + case 1: case 2: + case 4: case 8: + break; + default: + TIFFError(ctx->filename, "Can not handle %d-bit/sample image", + ctx->bitspersample); + return (0); + } + if (ctx->planarconfiguration == PLANARCONFIG_SEPARATE && + ctx->extrasamples > 0) + TIFFWarning(ctx->filename, "Ignoring extra samples"); + return (1); +} + +#define PS_UNIT_SIZE 72.0F +#define PSUNITS(npix,res) ((npix) * (PS_UNIT_SIZE / (res))) + +static char RGBcolorimage[] = "\ +/bwproc {\n\ + rgbproc\n\ + dup length 3 idiv string 0 3 0\n\ + 5 -1 roll {\n\ + add 2 1 roll 1 sub dup 0 eq {\n\ + pop 3 idiv\n\ + 3 -1 roll\n\ + dup 4 -1 roll\n\ + dup 3 1 roll\n\ + 5 -1 roll put\n\ + 1 add 3 0\n\ + } { 2 1 roll } ifelse\n\ + } forall\n\ + pop pop pop\n\ +} def\n\ +/colorimage where {pop} {\n\ + /colorimage {pop pop /rgbproc exch def {bwproc} image} bind def\n\ +} ifelse\n\ +"; + +/* + * Adobe Photoshop requires a comment line of the form: + * + * %ImageData:
+ * <1 for binary|2 for hex> "data start" + * + * It is claimed to be part of some future revision of the EPS spec. + */ +static void +PhotoshopBanner(TIFF2PSContext* ctx, uint32 w, uint32 h, int bs, int nc, + char* startline) +{ + fprintf(ctx->fd, "%%ImageData: %ld %ld %d %d 0 %d 2 \"", + (long) w, (long) h, ctx->bitspersample, nc, bs); + fprintf(ctx->fd, startline, nc); + fprintf(ctx->fd, "\"\n"); +} + +/* + * pw : image width in pixels + * ph : image height in pixels + * pprw : image width in PS units (72 dpi) + * pprh : image height in PS units (72 dpi) + */ +static void +setupPageState(TIFF2PSContext *ctx, TIFF* tif, uint32* pw, uint32* ph, + double* pprw, double* pprh) +{ + float xres = 0.0F, yres = 0.0F; + + TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, pw); + TIFFGetField(tif, TIFFTAG_IMAGELENGTH, ph); + if (ctx->res_unit == 0) + TIFFGetFieldDefaulted(tif, TIFFTAG_RESOLUTIONUNIT, &ctx->res_unit); + /* + * Calculate printable area. + */ + if (!TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xres) + || fabs(xres) < 0.0000001) + xres = PS_UNIT_SIZE; + if (!TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yres) + || fabs(yres) < 0.0000001) + yres = PS_UNIT_SIZE; + switch (ctx->res_unit) { + case RESUNIT_CENTIMETER: + xres *= 2.54F, yres *= 2.54F; + break; + case RESUNIT_INCH: + break; + case RESUNIT_NONE: + default: + xres *= PS_UNIT_SIZE, yres *= PS_UNIT_SIZE; + break; + } + *pprh = PSUNITS(*ph, yres); + *pprw = PSUNITS(*pw, xres); +} + +static int +isCCITTCompression(TIFF* tif) +{ + uint16 compress; + TIFFGetField(tif, TIFFTAG_COMPRESSION, &compress); + return (compress == COMPRESSION_CCITTFAX3 || + compress == COMPRESSION_CCITTFAX4 || + compress == COMPRESSION_CCITTRLE || + compress == COMPRESSION_CCITTRLEW); +} + +static char *hex = "0123456789abcdef"; + +/* + * imagewidth & imageheight are 1/72 inches + * pagewidth & pageheight are inches + */ +static int +PlaceImage(TIFF2PSContext *ctx, double pagewidth, double pageheight, + double imagewidth, double imageheight, int splitpage, + double lm, double bm, int cnt) +{ + double xtran = 0; + double ytran = 0; + double xscale = 1; + double yscale = 1; + double left_offset = lm * PS_UNIT_SIZE; + double bottom_offset = bm * PS_UNIT_SIZE; + double subimageheight; + double splitheight; + double overlap; + /* buffers for locale-insitive number formatting */ + gchar buf[2][G_ASCII_DTOSTR_BUF_SIZE]; + + pagewidth *= PS_UNIT_SIZE; + pageheight *= PS_UNIT_SIZE; + + if (ctx->maxPageHeight==0) + splitheight = 0; + else + splitheight = ctx->maxPageHeight * PS_UNIT_SIZE; + overlap = ctx->splitOverlap * PS_UNIT_SIZE; + + /* + * WIDTH: + * if too wide, scrunch to fit + * else leave it alone + */ + if (imagewidth <= pagewidth) { + xscale = imagewidth; + } else { + xscale = pagewidth; + } + + /* HEIGHT: + * if too long, scrunch to fit + * if too short, move to top of page + */ + if (imageheight <= pageheight) { + yscale = imageheight; + ytran = pageheight - imageheight; + } else if (imageheight > pageheight && + (splitheight == 0 || imageheight <= splitheight)) { + yscale = pageheight; + } else /* imageheight > splitheight */ { + subimageheight = imageheight - (pageheight-overlap)*splitpage; + if (subimageheight <= pageheight) { + yscale = imageheight; + ytran = pageheight - subimageheight; + splitpage = 0; + } else if ( subimageheight > pageheight && subimageheight <= splitheight) { + yscale = imageheight * pageheight / subimageheight; + ytran = 0; + splitpage = 0; + } else /* sumimageheight > splitheight */ { + yscale = imageheight; + ytran = pageheight - subimageheight; + splitpage++; + } + } + + bottom_offset += ytran / (cnt?2:1); + if (cnt) + left_offset += xtran / 2; + + fprintf(ctx->fd, "%s %s translate\n", + g_ascii_dtostr(buf[0], sizeof(buf[0]), left_offset), + g_ascii_dtostr(buf[1], sizeof(buf[1]), bottom_offset)); + fprintf(ctx->fd, "%s %s scale\n", + g_ascii_dtostr(buf[0], sizeof(buf[0]), xscale), + g_ascii_dtostr(buf[1], sizeof(buf[1]), yscale)); + if (ctx->rotate) + fputs ("1 1 translate 180 ctx->rotate\n", ctx->fd); + + return splitpage; +} + + +void +tiff2ps_process_page(TIFF2PSContext* ctx, TIFF* tif, double pw, double ph, + double lm, double bm, gboolean cnt) +{ + uint32 w, h; + float ox, oy; + double prw, prh; + double scale = 1.0; + double left_offset = lm * PS_UNIT_SIZE; + double bottom_offset = bm * PS_UNIT_SIZE; + uint16* sampleinfo; + int split; + /* buffers for locale-insitive number formatting */ + gchar buf[2][G_ASCII_DTOSTR_BUF_SIZE]; + + if (!TIFFGetField(tif, TIFFTAG_XPOSITION, &ox)) + ox = 0; + if (!TIFFGetField(tif, TIFFTAG_YPOSITION, &oy)) + oy = 0; + setupPageState(ctx, tif, &w, &h, &prw, &prh); + + ctx->tf_numberstrips = TIFFNumberOfStrips(tif); + TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, + &ctx->tf_rowsperstrip); + setupPageState(ctx, tif, &w, &h, &prw, &prh); + if (!ctx->npages) + PSHead(ctx, tif, w, h, prw, prh, ox, oy); + TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, + &ctx->bitspersample); + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, + &ctx->samplesperpixel); + TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, + &ctx->planarconfiguration); + TIFFGetField(tif, TIFFTAG_COMPRESSION, &ctx->compression); + TIFFGetFieldDefaulted(tif, TIFFTAG_EXTRASAMPLES, + &ctx->extrasamples, &sampleinfo); + ctx->alpha = (ctx->extrasamples == 1 && + sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA); + if (!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &ctx->photometric)) { + switch (ctx->samplesperpixel - ctx->extrasamples) { + case 1: + if (isCCITTCompression(tif)) + ctx->photometric = PHOTOMETRIC_MINISWHITE; + else + ctx->photometric = PHOTOMETRIC_MINISBLACK; + break; + case 3: + ctx->photometric = PHOTOMETRIC_RGB; + break; + case 4: + ctx->photometric = PHOTOMETRIC_SEPARATED; + break; + } + } + if (checkImage(ctx, tif)) { + ctx->tf_bytesperrow = TIFFScanlineSize(tif); + ctx->npages++; + fprintf(ctx->fd, "%%%%Page: %d %d\n", ctx->npages, + ctx->npages); + if (!ctx->generateEPSF && ( ctx->level2 || ctx->level3 )) { + double psw = 0.0, psh = 0.0; + if (psw != 0.0) { + psw = pw * PS_UNIT_SIZE; + if (ctx->res_unit == RESUNIT_CENTIMETER) + psw *= 2.54F; + } else + psw=ctx->rotate ? prh:prw; + if (psh != 0.0) { + psh = ph * PS_UNIT_SIZE; + if (ctx->res_unit == RESUNIT_CENTIMETER) + psh *= 2.54F; + } else + psh=ctx->rotate ? prw:prh; + fprintf(ctx->fd, + "1 dict begin /PageSize [ %s %s ] def currentdict end setpagedevice\n", + g_ascii_dtostr(buf[0], sizeof(buf[0]), psw), + g_ascii_dtostr(buf[1], sizeof(buf[1]), psh)); + fputs( + "<<\n /Policies <<\n /PageSize 3\n >>\n>> setpagedevice\n", + ctx->fd); + } + fprintf(ctx->fd, "gsave\n"); + fprintf(ctx->fd, "100 dict begin\n"); + if (pw != 0 || ph != 0) { + if (!pw) + pw = prw; + if (!ph) + ph = prh; + if (ctx->maxPageHeight) { /* used -H option */ + split = PlaceImage(ctx,pw,ph,prw,prh, + 0,lm,bm,cnt); + while( split ) { + PSpage(ctx, tif, w, h); + fprintf(ctx->fd, "end\n"); + fprintf(ctx->fd, "grestore\n"); + fprintf(ctx->fd, "showpage\n"); + ctx->npages++; + fprintf(ctx->fd, "%%%%Page: %d %d\n", + ctx->npages, ctx->npages); + fprintf(ctx->fd, "gsave\n"); + fprintf(ctx->fd, "100 dict begin\n"); + split = PlaceImage(ctx,pw,ph,prw,prh, + split,lm,bm,cnt); + } + } else { + pw *= PS_UNIT_SIZE; + ph *= PS_UNIT_SIZE; + + /* NB: maintain image aspect ratio */ + scale = pw/prw < ph/prh ? + pw/prw : ph/prh; + if (scale > 1.0) + scale = 1.0; + if (cnt) { + bottom_offset += + (ph - prh * scale) / 2; + left_offset += + (pw - prw * scale) / 2; + } + fprintf(ctx->fd, "%s %s translate\n", + g_ascii_dtostr(buf[0], sizeof(buf[0]), left_offset), + g_ascii_dtostr(buf[1], sizeof(buf[1]), bottom_offset)); + fprintf(ctx->fd, "%s %s scale\n", + g_ascii_dtostr(buf[0], sizeof(buf[0]), prw * scale), + g_ascii_dtostr(buf[1], sizeof(buf[1]), prh * scale)); + if (ctx->rotate) + fputs ("1 1 translate 180 ctx->rotate\n", ctx->fd); + } + } else { + fprintf(ctx->fd, "%s %s scale\n", + g_ascii_dtostr(buf[0], sizeof(buf[0]), prw), + g_ascii_dtostr(buf[1], sizeof(buf[1]), prh)); + if (ctx->rotate) + fputs ("1 1 translate 180 ctx->rotate\n", ctx->fd); + } + PSpage(ctx, tif, w, h); + fprintf(ctx->fd, "end\n"); + fprintf(ctx->fd, "grestore\n"); + fprintf(ctx->fd, "showpage\n"); + } +} + + +static char DuplexPreamble[] = "\ +%%BeginFeature: *Duplex True\n\ +systemdict begin\n\ + /languagelevel where { pop languagelevel } { 1 } ifelse\n\ + 2 ge { 1 dict dup /Duplex true put setpagedevice }\n\ + { statusdict /setduplex known { statusdict begin setduplex true end } if\n\ + } ifelse\n\ +end\n\ +%%EndFeature\n\ +"; + +static char TumblePreamble[] = "\ +%%BeginFeature: *Tumble True\n\ +systemdict begin\n\ + /languagelevel where { pop languagelevel } { 1 } ifelse\n\ + 2 ge { 1 dict dup /Tumble true put setpagedevice }\n\ + { statusdict /settumble known { statusdict begin true settumble end } if\n\ + } ifelse\n\ +end\n\ +%%EndFeature\n\ +"; + +static char AvoidDeadZonePreamble[] = "\ +gsave newpath clippath pathbbox grestore\n\ + 4 2 roll 2 copy translate\n\ + exch 3 1 roll sub 3 1 roll sub exch\n\ + currentpagedevice /PageSize get aload pop\n\ + exch 3 1 roll div 3 1 roll div abs exch abs\n\ + 2 copy gt { exch } if pop\n\ + dup 1 lt { dup scale } { pop } ifelse\n\ +"; + +void +PSHead(TIFF2PSContext *ctx, TIFF *tif, uint32 w, uint32 h, + double pw, double ph, double ox, double oy) +{ + time_t t; + + (void) tif; (void) w; (void) h; + t = time(0); + fprintf(ctx->fd, "%%!PS-Adobe-3.0%s\n", + ctx->generateEPSF ? " EPSF-3.0" : ""); + fprintf(ctx->fd, "%%%%Creator: Evince\n"); + fprintf(ctx->fd, "%%%%CreationDate: %s", ctime(&t)); + fprintf(ctx->fd, "%%%%DocumentData: Clean7Bit\n"); + fprintf(ctx->fd, "%%%%Origin: %ld %ld\n", (long) ox, (long) oy); + /* NB: should use PageBoundingBox */ + fprintf(ctx->fd, "%%%%BoundingBox: 0 0 %ld %ld\n", + (long) ceil(pw), (long) ceil(ph)); + fprintf(ctx->fd, "%%%%LanguageLevel: %d\n", + (ctx->level3 ? 3 : (ctx->level2 ? 2 : 1))); + fprintf(ctx->fd, "%%%%Pages: (atend)\n"); + fprintf(ctx->fd, "%%%%EndComments\n"); + fprintf(ctx->fd, "%%%%BeginSetup\n"); + if (ctx->PSduplex) + fprintf(ctx->fd, "%s", DuplexPreamble); + if (ctx->PStumble) + fprintf(ctx->fd, "%s", TumblePreamble); + if (ctx->PSavoiddeadzone && (ctx->level2 || ctx->level3)) + fprintf(ctx->fd, "%s", AvoidDeadZonePreamble); + fprintf(ctx->fd, "%%%%EndSetup\n"); +} + +static void +PSTail(TIFF2PSContext *ctx) +{ + if (!ctx->npages) + return; + fprintf(ctx->fd, "%%%%Trailer\n"); + fprintf(ctx->fd, "%%%%Pages: %d\n", ctx->npages); + fprintf(ctx->fd, "%%%%EOF\n"); +} + +static int +checkcmap(TIFF2PSContext* ctx, TIFF* tif, int n, + uint16* r, uint16* g, uint16* b) +{ + (void) tif; + while (n-- > 0) + if (*r++ >= 256 || *g++ >= 256 || *b++ >= 256) + return (16); + TIFFWarning(ctx->filename, "Assuming 8-bit colormap"); + return (8); +} + +static void +PS_Lvl2colorspace(TIFF2PSContext* ctx, TIFF* tif) +{ + uint16 *rmap, *gmap, *bmap; + int i, num_colors; + const char * colorspace_p; + + switch ( ctx->photometric ) + { + case PHOTOMETRIC_SEPARATED: + colorspace_p = "CMYK"; + break; + + case PHOTOMETRIC_RGB: + colorspace_p = "RGB"; + break; + + default: + colorspace_p = "Gray"; + } + + /* + * Set up PostScript Level 2 colorspace according to + * section 4.8 in the PostScript refenence manual. + */ + fputs("% PostScript Level 2 only.\n", ctx->fd); + if (ctx->photometric != PHOTOMETRIC_PALETTE) { + if (ctx->photometric == PHOTOMETRIC_YCBCR) { + /* MORE CODE HERE */ + } + fprintf(ctx->fd, "/Device%s setcolorspace\n", colorspace_p ); + return; + } + + /* + * Set up an indexed/palette colorspace + */ + num_colors = (1 << ctx->bitspersample); + if (!TIFFGetField(tif, TIFFTAG_COLORMAP, &rmap, &gmap, &bmap)) { + TIFFError(ctx->filename, + "Palette image w/o \"Colormap\" tag"); + return; + } + if (checkcmap(ctx, tif, num_colors, rmap, gmap, bmap) == 16) { + /* + * Convert colormap to 8-bits values. + */ +#define CVT(x) (((x) * 255) / ((1L<<16)-1)) + for (i = 0; i < num_colors; i++) { + rmap[i] = CVT(rmap[i]); + gmap[i] = CVT(gmap[i]); + bmap[i] = CVT(bmap[i]); + } +#undef CVT + } + fprintf(ctx->fd, "[ /Indexed /DeviceRGB %d", num_colors - 1); + if (ctx->ascii85) { + Ascii85Init(ctx); + fputs("\n<~", ctx->fd); + ctx->ascii85breaklen -= 2; + } else + fputs(" <", ctx->fd); + for (i = 0; i < num_colors; i++) { + if (ctx->ascii85) { + Ascii85Put(ctx, (unsigned char)rmap[i]); + Ascii85Put(ctx, (unsigned char)gmap[i]); + Ascii85Put(ctx, (unsigned char)bmap[i]); + } else { + fputs((i % 8) ? " " : "\n ", ctx->fd); + fprintf(ctx->fd, "%02x%02x%02x", + rmap[i], gmap[i], bmap[i]); + } + } + if (ctx->ascii85) + Ascii85Flush(ctx); + else + fputs(">\n", ctx->fd); + fputs("] setcolorspace\n", ctx->fd); +} + +static int +PS_Lvl2ImageDict(TIFF2PSContext* ctx, TIFF* tif, uint32 w, uint32 h) +{ + int use_rawdata; + uint32 tile_width, tile_height; + uint16 predictor, minsamplevalue, maxsamplevalue; + int repeat_count; + char im_h[64], im_x[64], im_y[64]; + char * imageOp = "image"; + + if ( ctx->useImagemask && (ctx->bitspersample == 1) ) + imageOp = "imagemask"; + + (void)strcpy(im_x, "0"); + (void)sprintf(im_y, "%lu", (long) h); + (void)sprintf(im_h, "%lu", (long) h); + tile_width = w; + tile_height = h; + if (TIFFIsTiled(tif)) { + repeat_count = TIFFNumberOfTiles(tif); + TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width); + TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height); + if (tile_width > w || tile_height > h || + (w % tile_width) != 0 || (h % tile_height != 0)) { + /* + * The tiles does not fit image width and height. + * Set up a clip rectangle for the image unit square. + */ + fputs("0 0 1 1 rectclip\n", ctx->fd); + } + if (tile_width < w) { + fputs("/im_x 0 def\n", ctx->fd); + (void)strcpy(im_x, "im_x neg"); + } + if (tile_height < h) { + fputs("/im_y 0 def\n", ctx->fd); + (void)sprintf(im_y, "%lu im_y sub", (unsigned long) h); + } + } else { + repeat_count = ctx->tf_numberstrips; + tile_height = ctx->tf_rowsperstrip; + if (tile_height > h) + tile_height = h; + if (repeat_count > 1) { + fputs("/im_y 0 def\n", ctx->fd); + fprintf(ctx->fd, "/im_h %lu def\n", + (unsigned long) tile_height); + (void)strcpy(im_h, "im_h"); + (void)sprintf(im_y, "%lu im_y sub", (unsigned long) h); + } + } + + /* + * Output start of exec block + */ + fputs("{ % exec\n", ctx->fd); + + if (repeat_count > 1) + fprintf(ctx->fd, "%d { %% repeat\n", repeat_count); + + /* + * Output filter options and image dictionary. + */ + if (ctx->ascii85) + fputs(" /im_stream currentfile /ASCII85Decode filter def\n", + ctx->fd); + fputs(" <<\n", ctx->fd); + fputs(" /ImageType 1\n", ctx->fd); + fprintf(ctx->fd, " /Width %lu\n", (unsigned long) tile_width); + /* + * Workaround for some software that may crash when last strip + * of image contains fewer number of scanlines than specified + * by the `/Height' variable. So for stripped images with multiple + * strips we will set `/Height' as `im_h', because one is + * recalculated for each strip - including the (smaller) final strip. + * For tiled images and images with only one strip `/Height' will + * contain number of scanlines in tile (or image height in case of + * one-stripped image). + */ + if (TIFFIsTiled(tif) || ctx->tf_numberstrips == 1) + fprintf(ctx->fd, " /Height %lu\n", (unsigned long) tile_height); + else + fprintf(ctx->fd, " /Height im_h\n"); + + if (ctx->planarconfiguration == PLANARCONFIG_SEPARATE && ctx->samplesperpixel > 1) + fputs(" /MultipleDataSources true\n", ctx->fd); + fprintf(ctx->fd, " /ImageMatrix [ %lu 0 0 %ld %s %s ]\n", + (unsigned long) w, - (long)h, im_x, im_y); + fprintf(ctx->fd, " /BitsPerComponent %d\n", ctx->bitspersample); + fprintf(ctx->fd, " /Ctx->Interpolate %s\n", ctx->interpolate ? "true" : "false"); + + switch (ctx->samplesperpixel - ctx->extrasamples) { + case 1: + switch (ctx->photometric) { + case PHOTOMETRIC_MINISBLACK: + fputs(" /Decode [0 1]\n", ctx->fd); + break; + case PHOTOMETRIC_MINISWHITE: + switch (ctx->compression) { + case COMPRESSION_CCITTRLE: + case COMPRESSION_CCITTRLEW: + case COMPRESSION_CCITTFAX3: + case COMPRESSION_CCITTFAX4: + /* + * Manage inverting with /Blackis1 flag + * since there migth be uncompressed parts + */ + fputs(" /Decode [0 1]\n", ctx->fd); + break; + default: + /* + * ERROR... + */ + fputs(" /Decode [1 0]\n", ctx->fd); + break; + } + break; + case PHOTOMETRIC_PALETTE: + TIFFGetFieldDefaulted(tif, TIFFTAG_MINSAMPLEVALUE, + &minsamplevalue); + TIFFGetFieldDefaulted(tif, TIFFTAG_MAXSAMPLEVALUE, + &maxsamplevalue); + fprintf(ctx->fd, " /Decode [%u %u]\n", + minsamplevalue, maxsamplevalue); + break; + default: + /* + * ERROR ? + */ + fputs(" /Decode [0 1]\n", ctx->fd); + break; + } + break; + case 3: + switch (ctx->photometric) { + case PHOTOMETRIC_RGB: + fputs(" /Decode [0 1 0 1 0 1]\n", ctx->fd); + break; + case PHOTOMETRIC_MINISWHITE: + case PHOTOMETRIC_MINISBLACK: + default: + /* + * ERROR?? + */ + fputs(" /Decode [0 1 0 1 0 1]\n", ctx->fd); + break; + } + break; + case 4: + /* + * ERROR?? + */ + fputs(" /Decode [0 1 0 1 0 1 0 1]\n", ctx->fd); + break; + } + fputs(" /DataSource", ctx->fd); + if (ctx->planarconfiguration == PLANARCONFIG_SEPARATE && + ctx->samplesperpixel > 1) + fputs(" [", ctx->fd); + if (ctx->ascii85) + fputs(" im_stream", ctx->fd); + else + fputs(" currentfile /ASCIIHexDecode filter", ctx->fd); + + use_rawdata = TRUE; + switch (ctx->compression) { + case COMPRESSION_NONE: /* 1: uncompressed */ + break; + case COMPRESSION_CCITTRLE: /* 2: CCITT modified Huffman RLE */ + case COMPRESSION_CCITTRLEW: /* 32771: #1 w/ word alignment */ + case COMPRESSION_CCITTFAX3: /* 3: CCITT Group 3 fax encoding */ + case COMPRESSION_CCITTFAX4: /* 4: CCITT Group 4 fax encoding */ + fputs("\n\t<<\n", ctx->fd); + if (ctx->compression == COMPRESSION_CCITTFAX3) { + uint32 g3_options; + + fputs("\t /EndOfLine true\n", ctx->fd); + fputs("\t /EndOfBlock false\n", ctx->fd); + if (!TIFFGetField(tif, TIFFTAG_GROUP3OPTIONS, + &g3_options)) + g3_options = 0; + if (g3_options & GROUP3OPT_2DENCODING) + fprintf(ctx->fd, "\t /K %s\n", im_h); + if (g3_options & GROUP3OPT_UNCOMPRESSED) + fputs("\t /Uncompressed true\n", ctx->fd); + if (g3_options & GROUP3OPT_FILLBITS) + fputs("\t /EncodedByteAlign true\n", ctx->fd); + } + if (ctx->compression == COMPRESSION_CCITTFAX4) { + uint32 g4_options; + + fputs("\t /K -1\n", ctx->fd); + TIFFGetFieldDefaulted(tif, TIFFTAG_GROUP4OPTIONS, + &g4_options); + if (g4_options & GROUP4OPT_UNCOMPRESSED) + fputs("\t /Uncompressed true\n", ctx->fd); + } + if (!(tile_width == w && w == 1728U)) + fprintf(ctx->fd, "\t /Columns %lu\n", + (unsigned long) tile_width); + fprintf(ctx->fd, "\t /Rows %s\n", im_h); + if (ctx->compression == COMPRESSION_CCITTRLE || + ctx->compression == COMPRESSION_CCITTRLEW) { + fputs("\t /EncodedByteAlign true\n", ctx->fd); + fputs("\t /EndOfBlock false\n", ctx->fd); + } + if (ctx->photometric == PHOTOMETRIC_MINISBLACK) + fputs("\t /BlackIs1 true\n", ctx->fd); + fprintf(ctx->fd, "\t>> /CCITTFaxDecode filter"); + break; + case COMPRESSION_LZW: /* 5: Lempel-Ziv & Welch */ + TIFFGetFieldDefaulted(tif, TIFFTAG_PREDICTOR, &predictor); + if (predictor == 2) { + fputs("\n\t<<\n", ctx->fd); + fprintf(ctx->fd, "\t /Predictor %u\n", predictor); + fprintf(ctx->fd, "\t /Columns %lu\n", + (unsigned long) tile_width); + fprintf(ctx->fd, "\t /Colors %u\n", ctx->samplesperpixel); + fprintf(ctx->fd, "\t /BitsPerComponent %u\n", + ctx->bitspersample); + fputs("\t>>", ctx->fd); + } + fputs(" /LZWDecode filter", ctx->fd); + break; + case COMPRESSION_DEFLATE: /* 5: ZIP */ + case COMPRESSION_ADOBE_DEFLATE: + if ( ctx->level3 ) { + TIFFGetFieldDefaulted(tif, TIFFTAG_PREDICTOR, &predictor); + if (predictor > 1) { + fprintf(ctx->fd, "\t %% PostScript Level 3 only."); + fputs("\n\t<<\n", ctx->fd); + fprintf(ctx->fd, "\t /Predictor %u\n", predictor); + fprintf(ctx->fd, "\t /Columns %lu\n", + (unsigned long) tile_width); + fprintf(ctx->fd, "\t /Colors %u\n", ctx->samplesperpixel); + fprintf(ctx->fd, "\t /BitsPerComponent %u\n", + ctx->bitspersample); + fputs("\t>>", ctx->fd); + } + fputs(" /FlateDecode filter", ctx->fd); + } else { + use_rawdata = FALSE ; + } + break; + case COMPRESSION_PACKBITS: /* 32773: Macintosh RLE */ + fputs(" /RunLengthDecode filter", ctx->fd); + use_rawdata = TRUE; + break; + case COMPRESSION_OJPEG: /* 6: !6.0 JPEG */ + case COMPRESSION_JPEG: /* 7: %JPEG DCT ctx->compression */ +#ifdef notdef + /* + * Code not tested yet + */ + fputs(" /DCTDecode filter", ctx->fd); + use_rawdata = TRUE; +#else + use_rawdata = FALSE; +#endif + break; + case COMPRESSION_NEXT: /* 32766: NeXT 2-bit RLE */ + case COMPRESSION_THUNDERSCAN: /* 32809: ThunderScan RLE */ + case COMPRESSION_PIXARFILM: /* 32908: Pixar companded 10bit LZW */ + case COMPRESSION_JBIG: /* 34661: ISO JBIG */ + use_rawdata = FALSE; + break; + case COMPRESSION_SGILOG: /* 34676: SGI LogL or LogLuv */ + case COMPRESSION_SGILOG24: /* 34677: SGI 24-bit LogLuv */ + use_rawdata = FALSE; + break; + default: + /* + * ERROR... + */ + use_rawdata = FALSE; + break; + } + if (ctx->planarconfiguration == PLANARCONFIG_SEPARATE && + ctx->samplesperpixel > 1) { + uint16 i; + + /* + * NOTE: This code does not work yet... + */ + for (i = 1; i < ctx->samplesperpixel; i++) + fputs(" dup", ctx->fd); + fputs(" ]", ctx->fd); + } + + fprintf( ctx->fd, "\n >> %s\n", imageOp ); + if (ctx->ascii85) + fputs(" im_stream status { im_stream flushfile } if\n", ctx->fd); + if (repeat_count > 1) { + if (tile_width < w) { + fprintf(ctx->fd, " /im_x im_x %lu add def\n", + (unsigned long) tile_width); + if (tile_height < h) { + fprintf(ctx->fd, " im_x %lu ge {\n", + (unsigned long) w); + fputs(" /im_x 0 def\n", ctx->fd); + fprintf(ctx->fd, " /im_y im_y %lu add def\n", + (unsigned long) tile_height); + fputs(" } if\n", ctx->fd); + } + } + if (tile_height < h) { + if (tile_width >= w) { + fprintf(ctx->fd, " /im_y im_y %lu add def\n", + (unsigned long) tile_height); + if (!TIFFIsTiled(tif)) { + fprintf(ctx->fd, " /im_h %lu im_y sub", + (unsigned long) h); + fprintf(ctx->fd, " dup %lu gt { pop", + (unsigned long) tile_height); + fprintf(ctx->fd, " %lu } if def\n", + (unsigned long) tile_height); + } + } + } + fputs("} repeat\n", ctx->fd); + } + /* + * End of exec function + */ + fputs("}\n", ctx->fd); + + return(use_rawdata); +} + +#define MAXLINE 36 + +static int +PS_Lvl2page(TIFF2PSContext* ctx, TIFF* tif, uint32 w, uint32 h) +{ + uint16 fillorder; + int use_rawdata, tiled_image, breaklen = MAXLINE; + uint32 chunk_no, num_chunks, *bc; + unsigned char *buf_data, *cp; + tsize_t chunk_size, byte_count; + +#if defined( EXP_ASCII85ENCODER ) + int ascii85_l; /* Length, in bytes, of ascii85_p[] data */ + uint8 * ascii85_p = 0; /* Holds ASCII85 encoded data */ +#endif + + PS_Lvl2colorspace(ctx, tif); + use_rawdata = PS_Lvl2ImageDict(ctx, tif, w, h); + +/* See http://bugzilla.remotesensing.org/show_bug.cgi?id=80 */ +#ifdef ENABLE_BROKEN_BEGINENDDATA + fputs("%%BeginData:\n", ctx->fd); +#endif + fputs("exec\n", ctx->fd); + + tiled_image = TIFFIsTiled(tif); + if (tiled_image) { + num_chunks = TIFFNumberOfTiles(tif); + TIFFGetField(tif, TIFFTAG_TILEBYTECOUNTS, &bc); + } else { + num_chunks = TIFFNumberOfStrips(tif); + TIFFGetField(tif, TIFFTAG_STRIPBYTECOUNTS, &bc); + } + + if (use_rawdata) { + chunk_size = (tsize_t) bc[0]; + for (chunk_no = 1; chunk_no < num_chunks; chunk_no++) + if ((tsize_t) bc[chunk_no] > chunk_size) + chunk_size = (tsize_t) bc[chunk_no]; + } else { + if (tiled_image) + chunk_size = TIFFTileSize(tif); + else + chunk_size = TIFFStripSize(tif); + } + buf_data = (unsigned char *)_TIFFmalloc(chunk_size); + if (!buf_data) { + TIFFError(ctx->filename, "Can't alloc %u bytes for %s.", + chunk_size, tiled_image ? "tiles" : "strips"); + return(FALSE); + } + +#if defined( EXP_ASCII85ENCODER ) + if ( ctx->ascii85 ) { + /* + * Allocate a buffer to hold the ASCII85 encoded data. Note + * that it is allocated with sufficient room to hold the + * encoded data (5*chunk_size/4) plus the EOD marker (+8) + * and formatting line breaks. The line breaks are more + * than taken care of by using 6*chunk_size/4 rather than + * 5*chunk_size/4. + */ + + ascii85_p = _TIFFmalloc( (chunk_size+(chunk_size/2)) + 8 ); + + if ( !ascii85_p ) { + _TIFFfree( buf_data ); + + TIFFError( ctx->filename, + "Cannot allocate ASCII85 encoding buffer." ); + return ( FALSE ); + } + } +#endif + + TIFFGetFieldDefaulted(tif, TIFFTAG_FILLORDER, &fillorder); + for (chunk_no = 0; chunk_no < num_chunks; chunk_no++) { + if (ctx->ascii85) + Ascii85Init(ctx); + else + breaklen = MAXLINE; + if (use_rawdata) { + if (tiled_image) + byte_count = TIFFReadRawTile(tif, chunk_no, + buf_data, chunk_size); + else + byte_count = TIFFReadRawStrip(tif, chunk_no, + buf_data, chunk_size); + if (fillorder == FILLORDER_LSB2MSB) + TIFFReverseBits(buf_data, byte_count); + } else { + if (tiled_image) + byte_count = TIFFReadEncodedTile(tif, + chunk_no, buf_data, + chunk_size); + else + byte_count = TIFFReadEncodedStrip(tif, + chunk_no, buf_data, + chunk_size); + } + if (byte_count < 0) { + TIFFError(ctx->filename, "Can't read %s %d.", + tiled_image ? "tile" : "strip", chunk_no); + if (ctx->ascii85) + Ascii85Put(ctx, '\0'); + } + /* + * For images with ctx->alpha, matte against a white background; + * i.e. Cback * (1 - Aimage) where Cback = 1. We will fill the + * lower part of the buffer with the modified values. + * + * XXX: needs better solution + */ + if (ctx->alpha) { + int adjust, i, j = 0; + int ncomps = ctx->samplesperpixel - ctx->extrasamples; + for (i = 0; i < byte_count; i+=ctx->samplesperpixel) { + adjust = 255 - buf_data[i + ncomps]; + switch (ncomps) { + case 1: + buf_data[j++] = buf_data[i] + adjust; + break; + case 2: + buf_data[j++] = buf_data[i] + adjust; + buf_data[j++] = buf_data[i+1] + adjust; + break; + case 3: + buf_data[j++] = buf_data[i] + adjust; + buf_data[j++] = buf_data[i+1] + adjust; + buf_data[j++] = buf_data[i+2] + adjust; + break; + } + } + byte_count -= j; + } + + if (ctx->ascii85) { +#if defined( EXP_ASCII85ENCODER ) + ascii85_l = Ascii85EncodeBlock(ctx, ascii85_p, 1, + buf_data, byte_count); + + if ( ascii85_l > 0 ) + fwrite( ascii85_p, ascii85_l, 1, ctx->fd ); +#else + for (cp = buf_data; byte_count > 0; byte_count--) + Ascii85Put(ctx, *cp++); +#endif + } + else + { + for (cp = buf_data; byte_count > 0; byte_count--) { + putc(hex[((*cp)>>4)&0xf], ctx->fd); + putc(hex[(*cp)&0xf], ctx->fd); + cp++; + + if (--breaklen <= 0) { + putc('\n', ctx->fd); + breaklen = MAXLINE; + } + } + } + + if ( !ctx->ascii85 ) { + if ( ctx->level2 || ctx->level3 ) + putc( '>', ctx->fd ); + putc('\n', ctx->fd); + } +#if !defined( EXP_ASCII85ENCODER ) + else + Ascii85Flush(ctx); +#endif + } + +#if defined( EXP_ASCII85ENCODER ) + if ( ascii85_p ) + _TIFFfree( ascii85_p ); +#endif + _TIFFfree(buf_data); +#ifdef ENABLE_BROKEN_BEGINENDDATA + fputs("%%EndData\n", ctx->fd); +#endif + return(TRUE); +} + +void +PSpage(TIFF2PSContext* ctx, TIFF* tif, uint32 w, uint32 h) +{ + char *imageOp = "image"; + + if ( ctx->useImagemask && (ctx->bitspersample == 1) ) + imageOp = "imagemask"; + + if ((ctx->level2 || ctx->level3) && PS_Lvl2page(ctx, tif, w, h)) + return; + ctx->ps_bytesperrow = ctx->tf_bytesperrow - (ctx->extrasamples * ctx->bitspersample / 8)*w; + switch (ctx->photometric) { + case PHOTOMETRIC_RGB: + if (ctx->planarconfiguration == PLANARCONFIG_CONTIG) { + fprintf(ctx->fd, "%s", RGBcolorimage); + PSColorContigPreamble(ctx, w, h, 3); + PSDataColorContig(ctx, tif, w, h, 3); + } else { + PSColorSeparatePreamble(ctx, w, h, 3); + PSDataColorSeparate(ctx, tif, w, h, 3); + } + break; + case PHOTOMETRIC_SEPARATED: + /* XXX should emit CMYKcolorimage */ + if (ctx->planarconfiguration == PLANARCONFIG_CONTIG) { + PSColorContigPreamble(ctx, w, h, 4); + PSDataColorContig(ctx, tif, w, h, 4); + } else { + PSColorSeparatePreamble(ctx, w, h, 4); + PSDataColorSeparate(ctx, tif, w, h, 4); + } + break; + case PHOTOMETRIC_PALETTE: + fprintf(ctx->fd, "%s", RGBcolorimage); + PhotoshopBanner(ctx, w, h, 1, 3, "false 3 colorimage"); + fprintf(ctx->fd, "/scanLine %ld string def\n", + (long) ctx->ps_bytesperrow * 3L); + fprintf(ctx->fd, "%lu %lu 8\n", + (unsigned long) w, (unsigned long) h); + fprintf(ctx->fd, "[%lu 0 0 -%lu 0 %lu]\n", + (unsigned long) w, (unsigned long) h, + (unsigned long) h); + fprintf(ctx->fd, + "{currentfile scanLine readhexstring pop} bind\n"); + fprintf(ctx->fd, "false 3 colorimage\n"); + PSDataPalette(ctx, tif, w, h); + break; + case PHOTOMETRIC_MINISBLACK: + case PHOTOMETRIC_MINISWHITE: + PhotoshopBanner(ctx, w, h, 1, 1, imageOp); + fprintf(ctx->fd, "/scanLine %ld string def\n", + (long) ctx->ps_bytesperrow); + fprintf(ctx->fd, "%lu %lu %d\n", + (unsigned long) w, (unsigned long) h, ctx->bitspersample); + fprintf(ctx->fd, "[%lu 0 0 -%lu 0 %lu]\n", + (unsigned long) w, (unsigned long) h, (unsigned long) h); + fprintf(ctx->fd, + "{currentfile scanLine readhexstring pop} bind\n"); + fprintf(ctx->fd, "%s\n", imageOp); + PSDataBW(ctx, tif, w, h); + break; + } + putc('\n', ctx->fd); +} + +void +PSColorContigPreamble(TIFF2PSContext* ctx, uint32 w, uint32 h, int nc) +{ + ctx->ps_bytesperrow = nc * (ctx->tf_bytesperrow / ctx->samplesperpixel); + PhotoshopBanner(ctx, w, h, 1, nc, "false %d colorimage"); + fprintf(ctx->fd, "/line %ld string def\n", (long) ctx->ps_bytesperrow); + fprintf(ctx->fd, "%lu %lu %d\n", + (unsigned long) w, (unsigned long) h, ctx->bitspersample); + fprintf(ctx->fd, "[%lu 0 0 -%lu 0 %lu]\n", + (unsigned long) w, (unsigned long) h, (unsigned long) h); + fprintf(ctx->fd, "{currentfile line readhexstring pop} bind\n"); + fprintf(ctx->fd, "false %d colorimage\n", nc); +} + +void +PSColorSeparatePreamble(TIFF2PSContext* ctx, uint32 w, uint32 h, int nc) +{ + int i; + + PhotoshopBanner(ctx, w, h, ctx->ps_bytesperrow, nc, "true %d colorimage"); + for (i = 0; i < nc; i++) + fprintf(ctx->fd, "/line%d %ld string def\n", + i, (long) ctx->ps_bytesperrow); + fprintf(ctx->fd, "%lu %lu %d\n", + (unsigned long) w, (unsigned long) h, ctx->bitspersample); + fprintf(ctx->fd, "[%lu 0 0 -%lu 0 %lu] \n", + (unsigned long) w, (unsigned long) h, (unsigned long) h); + for (i = 0; i < nc; i++) + fprintf(ctx->fd, "{currentfile line%d readhexstring pop}bind\n", i); + fprintf(ctx->fd, "true %d colorimage\n", nc); +} + +#define DOBREAK(len, howmany, fd) \ + if (((len) -= (howmany)) <= 0) { \ + putc('\n', fd); \ + (len) = MAXLINE-(howmany); \ + } +#define PUTHEX(c,fd) putc(hex[((c)>>4)&0xf],fd); putc(hex[(c)&0xf],fd) + +void +PSDataColorContig(TIFF2PSContext* ctx, TIFF* tif, uint32 w, uint32 h, int nc) +{ + uint32 row; + int breaklen = MAXLINE, cc, es = ctx->samplesperpixel - nc; + unsigned char *tf_buf; + unsigned char *cp, c; + + (void) w; + tf_buf = (unsigned char *) _TIFFmalloc(ctx->tf_bytesperrow); + if (tf_buf == NULL) { + TIFFError(ctx->filename, "No space for scanline buffer"); + return; + } + for (row = 0; row < h; row++) { + if (TIFFReadScanline(tif, tf_buf, row, 0) < 0) + break; + cp = tf_buf; + if (ctx->alpha) { + int adjust; + cc = 0; + for (; cc < ctx->tf_bytesperrow; cc += ctx->samplesperpixel) { + DOBREAK(breaklen, nc, ctx->fd); + /* + * For images with ctx->alpha, matte against + * a white background; i.e. + * Cback * (1 - Aimage) + * where Cback = 1. + */ + adjust = 255 - cp[nc]; + switch (nc) { + case 4: c = *cp++ + adjust; PUTHEX(c,ctx->fd); + case 3: c = *cp++ + adjust; PUTHEX(c,ctx->fd); + case 2: c = *cp++ + adjust; PUTHEX(c,ctx->fd); + case 1: c = *cp++ + adjust; PUTHEX(c,ctx->fd); + } + cp += es; + } + } else { + cc = 0; + for (; cc < ctx->tf_bytesperrow; cc += ctx->samplesperpixel) { + DOBREAK(breaklen, nc, ctx->fd); + switch (nc) { + case 4: c = *cp++; PUTHEX(c,ctx->fd); + case 3: c = *cp++; PUTHEX(c,ctx->fd); + case 2: c = *cp++; PUTHEX(c,ctx->fd); + case 1: c = *cp++; PUTHEX(c,ctx->fd); + } + cp += es; + } + } + } + _TIFFfree((char *) tf_buf); +} + +void +PSDataColorSeparate(TIFF2PSContext* ctx, TIFF* tif, uint32 w, uint32 h, int nc) +{ + uint32 row; + int breaklen = MAXLINE, cc; + tsample_t s, maxs; + unsigned char *tf_buf; + unsigned char *cp, c; + + (void) w; + tf_buf = (unsigned char *) _TIFFmalloc(ctx->tf_bytesperrow); + if (tf_buf == NULL) { + TIFFError(ctx->filename, "No space for scanline buffer"); + return; + } + maxs = (ctx->samplesperpixel > nc ? nc : ctx->samplesperpixel); + for (row = 0; row < h; row++) { + for (s = 0; s < maxs; s++) { + if (TIFFReadScanline(tif, tf_buf, row, s) < 0) + break; + for (cp = tf_buf, cc = 0; cc < ctx->tf_bytesperrow; cc++) { + DOBREAK(breaklen, 1, ctx->fd); + c = *cp++; + PUTHEX(c,ctx->fd); + } + } + } + _TIFFfree((char *) tf_buf); +} + +#define PUTRGBHEX(c,fd) \ + PUTHEX(rmap[c],fd); PUTHEX(gmap[c],fd); PUTHEX(bmap[c],fd) + +void +PSDataPalette(TIFF2PSContext* ctx, TIFF* tif, uint32 w, uint32 h) +{ + uint16 *rmap, *gmap, *bmap; + uint32 row; + int breaklen = MAXLINE, cc, nc; + unsigned char *tf_buf; + unsigned char *cp, c; + + (void) w; + if (!TIFFGetField(tif, TIFFTAG_COLORMAP, &rmap, &gmap, &bmap)) { + TIFFError(ctx->filename, "Palette image w/o \"Colormap\" tag"); + return; + } + switch (ctx->bitspersample) { + case 8: case 4: case 2: case 1: + break; + default: + TIFFError(ctx->filename, "Depth %d not supported", ctx->bitspersample); + return; + } + nc = 3 * (8 / ctx->bitspersample); + tf_buf = (unsigned char *) _TIFFmalloc(ctx->tf_bytesperrow); + if (tf_buf == NULL) { + TIFFError(ctx->filename, "No space for scanline buffer"); + return; + } + if (checkcmap(ctx, tif, 1<bitspersample, rmap, gmap, bmap) == 16) { + int i; +#define CVT(x) ((unsigned short) (((x) * 255) / ((1U<<16)-1))) + for (i = (1<bitspersample)-1; i >= 0; i--) { + rmap[i] = CVT(rmap[i]); + gmap[i] = CVT(gmap[i]); + bmap[i] = CVT(bmap[i]); + } +#undef CVT + } + for (row = 0; row < h; row++) { + if (TIFFReadScanline(tif, tf_buf, row, 0) < 0) + break; + for (cp = tf_buf, cc = 0; cc < ctx->tf_bytesperrow; cc++) { + DOBREAK(breaklen, nc, ctx->fd); + switch (ctx->bitspersample) { + case 8: + c = *cp++; PUTRGBHEX(c, ctx->fd); + break; + case 4: + c = *cp++; PUTRGBHEX(c&0xf, ctx->fd); + c >>= 4; PUTRGBHEX(c, ctx->fd); + break; + case 2: + c = *cp++; PUTRGBHEX(c&0x3, ctx->fd); + c >>= 2; PUTRGBHEX(c&0x3, ctx->fd); + c >>= 2; PUTRGBHEX(c&0x3, ctx->fd); + c >>= 2; PUTRGBHEX(c, ctx->fd); + break; + case 1: + c = *cp++; PUTRGBHEX(c&0x1, ctx->fd); + c >>= 1; PUTRGBHEX(c&0x1, ctx->fd); + c >>= 1; PUTRGBHEX(c&0x1, ctx->fd); + c >>= 1; PUTRGBHEX(c&0x1, ctx->fd); + c >>= 1; PUTRGBHEX(c&0x1, ctx->fd); + c >>= 1; PUTRGBHEX(c&0x1, ctx->fd); + c >>= 1; PUTRGBHEX(c&0x1, ctx->fd); + c >>= 1; PUTRGBHEX(c, ctx->fd); + break; + } + } + } + _TIFFfree((char *) tf_buf); +} + +void +PSDataBW(TIFF2PSContext* ctx, TIFF* tif, uint32 w, uint32 h) +{ + int breaklen = MAXLINE; + unsigned char* tf_buf; + unsigned char* cp; + tsize_t stripsize = TIFFStripSize(tif); + tstrip_t s; + +#if defined( EXP_ASCII85ENCODER ) + int ascii85_l; /* Length, in bytes, of ascii85_p[] data */ + uint8 *ascii85_p = 0; /* Holds ASCII85 encoded data */ +#endif + + (void) w; (void) h; + tf_buf = (unsigned char *) _TIFFmalloc(stripsize); + memset(tf_buf, 0, stripsize); + if (tf_buf == NULL) { + TIFFError(ctx->filename, "No space for scanline buffer"); + return; + } + +#if defined( EXP_ASCII85ENCODER ) + if ( ctx->ascii85 ) { + /* + * Allocate a buffer to hold the ASCII85 encoded data. Note + * that it is allocated with sufficient room to hold the + * encoded data (5*stripsize/4) plus the EOD marker (+8) + * and formatting line breaks. The line breaks are more + * than taken care of by using 6*stripsize/4 rather than + * 5*stripsize/4. + */ + + ascii85_p = _TIFFmalloc( (stripsize+(stripsize/2)) + 8 ); + + if ( !ascii85_p ) { + _TIFFfree( tf_buf ); + + TIFFError( ctx->filename, + "Cannot allocate ASCII85 encoding buffer." ); + return; + } + } +#endif + + if (ctx->ascii85) + Ascii85Init(ctx); + + for (s = 0; s < TIFFNumberOfStrips(tif); s++) { + int cc = TIFFReadEncodedStrip(tif, s, tf_buf, stripsize); + if (cc < 0) { + TIFFError(ctx->filename, "Can't read strip"); + break; + } + cp = tf_buf; + if (ctx->photometric == PHOTOMETRIC_MINISWHITE) { + for (cp += cc; --cp >= tf_buf;) + *cp = ~*cp; + cp++; + } + if (ctx->ascii85) { +#if defined( EXP_ASCII85ENCODER ) + if (ctx->alpha) { + int adjust, i; + for (i = 0; i < cc; i+=2) { + adjust = 255 - cp[i + 1]; + cp[i / 2] = cp[i] + adjust; + } + cc /= 2; + } + + ascii85_l = Ascii85EncodeBlock(ctx, ascii85_p, 1, cp, + cc); + + if ( ascii85_l > 0 ) + fwrite( ascii85_p, ascii85_l, 1, ctx->fd ); +#else + while (cc-- > 0) + Ascii85Put(ctx, *cp++); +#endif /* EXP_ASCII85_ENCODER */ + } else { + unsigned char c; + + if (ctx->alpha) { + int adjust; + while (cc-- > 0) { + DOBREAK(breaklen, 1, ctx->fd); + /* + * For images with ctx->alpha, matte against + * a white background; i.e. + * Cback * (1 - Aimage) + * where Cback = 1. + */ + adjust = 255 - cp[1]; + c = *cp++ + adjust; PUTHEX(c,ctx->fd); + cp++, cc--; + } + } else { + while (cc-- > 0) { + c = *cp++; + DOBREAK(breaklen, 1, ctx->fd); + PUTHEX(c, ctx->fd); + } + } + } + } + + if ( !ctx->ascii85 ) + { + if ( ctx->level2 || ctx->level3) + fputs(">\n", ctx->fd); + } +#if !defined( EXP_ASCII85ENCODER ) + else + Ascii85Flush(ctx); +#else + if ( ascii85_p ) + _TIFFfree( ascii85_p ); +#endif + + _TIFFfree(tf_buf); +} + +static void +Ascii85Init(TIFF2PSContext *ctx) +{ + ctx->ascii85breaklen = 2*MAXLINE; + ctx->ascii85count = 0; +} + +static void +Ascii85Encode(unsigned char* raw, char *buf) +{ + uint32 word; + + word = (((raw[0]<<8)+raw[1])<<16) + (raw[2]<<8) + raw[3]; + if (word != 0L) { + uint32 q; + uint16 w1; + + q = word / (85L*85*85*85); /* actually only a byte */ + buf[0] = (char) (q + '!'); + + word -= q * (85L*85*85*85); q = word / (85L*85*85); + buf[1] = (char) (q + '!'); + + word -= q * (85L*85*85); q = word / (85*85); + buf[2] = (char) (q + '!'); + + w1 = (uint16) (word - q*(85L*85)); + buf[3] = (char) ((w1 / 85) + '!'); + buf[4] = (char) ((w1 % 85) + '!'); + buf[5] = '\0'; + } else + buf[0] = 'z', buf[1] = '\0'; +} + +void +Ascii85Put(TIFF2PSContext *ctx, unsigned char code) +{ + ctx->ascii85buf[ctx->ascii85count++] = code; + if (ctx->ascii85count >= 4) { + unsigned char* p; + int n; + char buf[6]; + + for (n = ctx->ascii85count, p = ctx->ascii85buf; + n >= 4; n -= 4, p += 4) { + char* cp; + Ascii85Encode(p, buf); + for (cp = buf; *cp; cp++) { + putc(*cp, ctx->fd); + if (--ctx->ascii85breaklen == 0) { + putc('\n', ctx->fd); + ctx->ascii85breaklen = 2*MAXLINE; + } + } + } + _TIFFmemcpy(ctx->ascii85buf, p, n); + ctx->ascii85count = n; + } +} + +void +Ascii85Flush(TIFF2PSContext* ctx) +{ + if (ctx->ascii85count > 0) { + char res[6]; + _TIFFmemset(&ctx->ascii85buf[ctx->ascii85count], 0, 3); + Ascii85Encode(ctx->ascii85buf, res); + fwrite(res[0] == 'z' ? "!!!!" : res, ctx->ascii85count + 1, 1, ctx->fd); + } + fputs("~>\n", ctx->fd); +} +#if defined( EXP_ASCII85ENCODER) + +#define A85BREAKCNTR ctx->ascii85breaklen +#define A85BREAKLEN (2*MAXLINE) + +/***************************************************************************** +* +* Name: Ascii85EncodeBlock( ascii85_p, f_eod, raw_p, raw_l ) +* +* Description: This routine will encode the raw data in the buffer described +* by raw_p and raw_l into ASCII85 format and store the encoding +* in the buffer given by ascii85_p. +* +* Parameters: ctx - TIFF2PS context +* ascii85_p - A buffer supplied by the caller which will +* contain the encoded ASCII85 data. +* f_eod - Flag: Nz means to end the encoded buffer with +* an End-Of-Data marker. +* raw_p - Pointer to the buffer of data to be encoded +* raw_l - Number of bytes in raw_p[] to be encoded +* +* Returns: (int) < 0 Error, see errno +* >= 0 Number of bytes written to ascii85_p[]. +* +* Notes: An external variable given by A85BREAKCNTR is used to +* determine when to insert newline characters into the +* encoded data. As each byte is placed into ascii85_p this +* external is decremented. If the variable is decrement to +* or past zero then a newline is inserted into ascii85_p +* and the A85BREAKCNTR is then reset to A85BREAKLEN. +* Note: for efficiency reasons the A85BREAKCNTR variable +* is not actually checked on *every* character +* placed into ascii85_p but often only for every +* 5 characters. +* +* THE CALLER IS RESPONSIBLE FOR ENSURING THAT ASCII85_P[] IS +* SUFFICIENTLY LARGE TO THE ENCODED DATA! +* You will need at least 5 * (raw_l/4) bytes plus space for +* newline characters and space for an EOD marker (if +* requested). A safe calculation is to use 6*(raw_l/4) + 8 +* to size ascii85_p. +* +*****************************************************************************/ + +int Ascii85EncodeBlock( TIFF2PSContext *ctx, uint8 * ascii85_p, + unsigned f_eod, const uint8 * raw_p, int raw_l ) + +{ + char ascii85[5]; /* Encoded 5 tuple */ + int ascii85_l; /* Number of bytes written to ascii85_p[] */ + int rc; /* Return code */ + uint32 val32; /* Unencoded 4 tuple */ + + ascii85_l = 0; /* Nothing written yet */ + + if ( raw_p ) + { + --raw_p; /* Prepare for pre-increment fetches */ + + for ( ; raw_l > 3; raw_l -= 4 ) + { + val32 = *(++raw_p) << 24; + val32 += *(++raw_p) << 16; + val32 += *(++raw_p) << 8; + val32 += *(++raw_p); + + if ( val32 == 0 ) /* Special case */ + { + ascii85_p[ascii85_l] = 'z'; + rc = 1; + } + + else + { + ascii85[4] = (char) ((val32 % 85) + 33); + val32 /= 85; + + ascii85[3] = (char) ((val32 % 85) + 33); + val32 /= 85; + + ascii85[2] = (char) ((val32 % 85) + 33); + val32 /= 85; + + ascii85[1] = (char) ((val32 % 85) + 33); + ascii85[0] = (char) ((val32 / 85) + 33); + + _TIFFmemcpy( &ascii85_p[ascii85_l], ascii85, sizeof(ascii85) ); + rc = sizeof(ascii85); + } + + ascii85_l += rc; + + if ( (A85BREAKCNTR -= rc) <= 0 ) + { + ascii85_p[ascii85_l] = '\n'; + ++ascii85_l; + A85BREAKCNTR = A85BREAKLEN; + } + } + + /* + * Output any straggler bytes: + */ + + if ( raw_l > 0 ) + { + int len; /* Output this many bytes */ + + len = raw_l + 1; + val32 = *++raw_p << 24; /* Prime the pump */ + + if ( --raw_l > 0 ) val32 += *(++raw_p) << 16; + if ( --raw_l > 0 ) val32 += *(++raw_p) << 8; + + val32 /= 85; + + ascii85[3] = (char) ((val32 % 85) + 33); + val32 /= 85; + + ascii85[2] = (char) ((val32 % 85) + 33); + val32 /= 85; + + ascii85[1] = (char) ((val32 % 85) + 33); + ascii85[0] = (char) ((val32 / 85) + 33); + + _TIFFmemcpy( &ascii85_p[ascii85_l], ascii85, len ); + ascii85_l += len; + } + } + + /* + * If requested add an ASCII85 End Of Data marker: + */ + + if ( f_eod ) + { + ascii85_p[ascii85_l++] = '~'; + ascii85_p[ascii85_l++] = '>'; + ascii85_p[ascii85_l++] = '\n'; + } + + return ( ascii85_l ); + +} /* Ascii85EncodeBlock() */ + +#endif /* EXP_ASCII85ENCODER */ + +/* vim: set ts=8 sts=8 sw=8 noet: */ diff --git a/backend/tiff/tiff2ps.h b/backend/tiff/tiff2ps.h new file mode 100644 index 00000000..575b881b --- /dev/null +++ b/backend/tiff/tiff2ps.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005 rpath, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include "tiffio.h" + +typedef struct _TIFF2PSContext TIFF2PSContext; + +TIFF2PSContext *tiff2ps_context_new(const gchar *filename); +void tiff2ps_process_page(TIFF2PSContext* ctx, TIFF* tif, + double pagewidth, double pageheight, + double leftmargin, double bottommargin, + gboolean center); +void tiff2ps_context_finalize(TIFF2PSContext* ctx); diff --git a/backend/tiff/tiffdocument.evince-backend.in b/backend/tiff/tiffdocument.evince-backend.in new file mode 100644 index 00000000..daaa7fd9 --- /dev/null +++ b/backend/tiff/tiffdocument.evince-backend.in @@ -0,0 +1,4 @@ +[Evince Backend] +Module=tiffdocument +_TypeDescription=Tiff Documents +MimeType=image/tiff diff --git a/config.h.in~ b/config.h.in~ new file mode 100644 index 00000000..c0f17b0f --- /dev/null +++ b/config.h.in~ @@ -0,0 +1,191 @@ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Enable support for comics. */ +#undef ENABLE_COMICS + +/* Define if DBUS support is enabled */ +#undef ENABLE_DBUS + +/* Enable djvu viewer support. */ +#undef ENABLE_DJVU + +/* Enable dvi viewer support. */ +#undef ENABLE_DVI + +/* Enable support for impress. */ +#undef ENABLE_IMPRESS + +/* always defined to indicate that i18n is enabled */ +#undef ENABLE_NLS + +/* Enable pixbuf support. */ +#undef ENABLE_PIXBUF + +/* Enable support for PostScript files. */ +#undef ENABLE_PS + +/* Enable multipage tiff support. */ +#undef ENABLE_TIFF + +/* Gettext package */ +#undef GETTEXT_PACKAGE + +/* Define if gtk+-unix-print is enabled. */ +#undef GTKUNIXPRINT_ENABLED + +/* Define to 1 if you have the `bind_textdomain_codeset' function. */ +#undef HAVE_BIND_TEXTDOMAIN_CODESET + +/* Define to 1 if you have the `cairo_format_stride_for_width' function. */ +#undef HAVE_CAIRO_FORMAT_STRIDE_FOR_WIDTH + +/* defined if cairo-pdf is available */ +#undef HAVE_CAIRO_PDF + +/* defined if cairo-ps is available */ +#undef HAVE_CAIRO_PS + +/* defined if you build the caja plugin */ +#undef HAVE_CAJA + +/* Define to 1 if you have the `dcgettext' function. */ +#undef HAVE_DCGETTEXT + +/* Define to 1 if you have the header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_EXECINFO_H + +/* Define if the GNU gettext() function is already present or preinstalled. */ +#undef HAVE_GETTEXT + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define if your file defines LC_MESSAGES. */ +#undef HAVE_LC_MESSAGES + +/* Define to 1 if you have the header file. */ +#undef HAVE_LOCALE_H + +/* Defines if localtime_r is available on your system */ +#undef HAVE_LOCALTIME_R + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `poppler_page_add_annot' function. */ +#undef HAVE_POPPLER_PAGE_ADD_ANNOT + +/* Define to 1 if you have the `poppler_page_get_selected_text' function. */ +#undef HAVE_POPPLER_PAGE_GET_SELECTED_TEXT + +/* Define to 1 if you have the `poppler_page_get_text_layout' function. */ +#undef HAVE_POPPLER_PAGE_GET_TEXT_LAYOUT + +/* Have libspectre */ +#undef HAVE_SPECTRE + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_TIFF_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define if _NL_MEASUREMENT_MEASUREMENT is available */ +#undef HAVE__NL_MEASUREMENT_MEASUREMENT + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#undef LT_OBJDIR + +/* Define to 1 if your C compiler doesn't accept -c and -o together. */ +#undef NO_MINUS_C_MINUS_O + +/* Enable offline help */ +#undef OFFLINE_HELP_ENABLED + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define if building for the hildon platform */ +#undef PLATFORM_HILDON + +/* The size of `int', as computed by sizeof. */ +#undef SIZEOF_INT + +/* The size of `long', as computed by sizeof. */ +#undef SIZEOF_LONG + +/* The size of `short', as computed by sizeof. */ +#undef SIZEOF_SHORT + +/* The size of `void *', as computed by sizeof. */ +#undef SIZEOF_VOID_P + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION + +/* Define if MateConf support is enabled */ +#undef WITH_MATECONF + +/* Define if KEYRING support is enabled */ +#undef WITH_KEYRING + +/* Define if smclient is enabled */ +#undef WITH_SMCLIENT + +/* Enable t1lib support in dvi. */ +#undef WITH_TYPE1_FONTS + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to `unsigned int' if does not define. */ +#undef size_t diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..a209b5ce --- /dev/null +++ b/configure.ac @@ -0,0 +1,884 @@ +# ***************************************************************************** +# Versioning +# ***************************************************************************** + +m4_define([ev_major_version],[2]) +m4_define([ev_minor_version],[32]) +m4_define([ev_micro_version],[0]) +m4_define([ev_extra_version],[]) +m4_define([ev_version],[ev_major_version.ev_minor_version.ev_micro_version()ev_extra_version]) + +# The evince API version +m4_define([ev_api_version], [2.32]) + +# Libtool versioning. The backend and view libraries have separate versions. +# Before making a release, the libtool version should be modified. +# The string is of the form C:R:A. +# - If interfaces have been changed or added, but binary compatibility has +# been preserved, change to C+1:0:A+1 +# - If binary compatibility has been broken (eg removed or changed interfaces) +# change to C+1:0:0 +# - If the interface is the same as the previous version, change to C:R+1:A + +# Libtool version of the backend library +m4_define([ev_document_lt_current],[3]) +m4_define([ev_document_lt_revision],[0]) +m4_define([ev_document_lt_age],[0]) +m4_define([ev_document_lt_version_info],[ev_document_lt_current:ev_document_lt_revision:ev_document_lt_age]) +m4_define([ev_document_lt_current_minus_age],[m4_eval(ev_document_lt_current - ev_document_lt_age)]) + +# Libtool version of the view library +m4_define([ev_view_lt_current],[3]) +m4_define([ev_view_lt_revision],[0]) +m4_define([ev_view_lt_age],[0]) +m4_define([ev_view_lt_version_info],[ev_view_lt_current:ev_view_lt_revision:ev_view_lt_age]) +m4_define([ev_view_lt_current_minus_age],[m4_eval(ev_view_lt_current - ev_view_lt_age)]) + +# Binary version for the document backends +m4_define([ev_binary_version],[ev_document_lt_current]) + +# ***************************************************************************** + +AC_PREREQ([2.57]) +AC_INIT([Evince],[ev_version],[http://bugzilla.mate.org/enter_bug.cgi?product=evince],[evince]) +AM_INIT_AUTOMAKE([1.10 foreign dist-bzip2 no-dist-gzip]) + +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +if test -z "$enable_maintainer_mode"; then + enable_maintainer_mode=yes +fi +AM_MAINTAINER_MODE([enable]) + +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) + +AM_PROG_LIBTOOL + +AC_ISC_POSIX +AC_PROG_CC +AM_PROG_CC_STDC +AM_PROG_CC_C_O +AC_PROG_CXX +AC_STDC_HEADERS + +AC_PROG_SED +AC_PATH_PROG([GLIB_GENMARSHAL], [glib-genmarshal]) +AC_PATH_PROG([GLIB_MKENUMS],[glib-mkenums]) + +MATE_MAINTAINER_MODE_DEFINES +MATE_COMPILE_WARNINGS +MATE_CXX_WARNINGS + +dnl FIXME: remove this when required gtk+ >= 2.19.7 +DISABLE_DEPRECATED= +AC_SUBST([DISABLE_DEPRECATED]) + +IT_PROG_INTLTOOL([0.35.0]) + +GETTEXT_PACKAGE=evince +AC_SUBST(GETTEXT_PACKAGE) +AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE],["$GETTEXT_PACKAGE"],[Gettext package]) +AM_GLIB_GNU_GETTEXT + +m4_pattern_allow([AM_V_GEN])dnl Make autoconf not complain about the rule below +EV_INTLTOOL_EVINCE_BACKEND_RULE='%.evince-backend: %.evince-backend.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(AM_V_GEN) LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' +AC_SUBST([EV_INTLTOOL_EVINCE_BACKEND_RULE]) + +GLIB_GSETTINGS + +# Check which platform to use + +AC_MSG_CHECKING([for which platform to build]) +AC_ARG_WITH([platform], + [AS_HELP_STRING([--with-platform=mate|win32|hildon], + [Setting platform (default: mate)])], + [case "$withval" in + mate|win32|hildon) ;; + *) AC_MSG_ERROR([invalid argument "$withval" for --with-platform]) ;; + esac], + [case "$host" in + *-*-mingw*|*-*-cygwin*) with_platform="win32" ;; + *) with_platform=mate ;; + esac]) + +AC_MSG_RESULT([$with_platform]) + +if test "$with_platform" = "win32"; then + AC_CHECK_TOOL([WINDRES],[windres]) + AC_MSG_CHECKING([for native Win32]) + case "$host" in + *-*-mingw*) + os_win32=yes + ;; + *) + os_win32=no + ;; + esac + AC_MSG_RESULT([$os_win32]) +fi + +AM_CONDITIONAL([PLATFORM_HILDON],[test "$with_platform" = "hildon"]) +AM_CONDITIONAL([PLATFORM_WIN32],[test "$with_platform" = "win32"]) + +AC_MSG_CHECKING([which gtk+ version to compile against]) +AC_ARG_WITH([gtk], + [AS_HELP_STRING([--with-gtk=2.0|3.0],[which gtk+ version to compile against (default: 2.0)])], + [case "$with_gtk" in + 2.0|3.0) ;; + *) AC_MSG_ERROR([invalid gtk version specified]) ;; + esac], + [with_gtk=2.0]) +AC_MSG_RESULT([$with_gtk]) + +dnl Specify required versions of dependencies +CAIRO_REQUIRED=1.9.10 +GLIB_REQUIRED=2.25.11 +KEYRING_REQUIRED=2.22.0 + +case "$with_gtk" in + 2.0) GTK_API_VERSION=2.0 + GAIL_API_VERSION= + GTK_REQUIRED=2.21.5 + ;; + 3.0) GTK_API_VERSION=3.0 + GAIL_API_VERSION=-3.0 + GTK_REQUIRED=2.90.5 + ;; +esac + +AC_SUBST([GLIB_REQUIRED]) +AC_SUBST([GTK_REQUIRED]) +AC_SUBST([GTK_API_VERSION]) + +MATE_ICON_THEME_REQUIRED=2.17.1 +LIBXML_REQUIRED=2.5.0 + +dnl Check dependencies + +# LIB_CFLAGS for helpers and generic widgets. (libdocument, cut-and-paste) +# BACKEND_CFLAGS for backend implementations. +# FRONTEND_CFLAGS for frontend implementations. (properties, thumbnailer) +# FRONTEND_LIBS +# SHELL_CFLAGS for shell implementation. +# SHELL_LIBS + + +PKG_CHECK_MODULES(LIBDOCUMENT, gtk+-$GTK_API_VERSION >= $GTK_REQUIRED gio-2.0 >= $GLIB_REQUIRED) +PKG_CHECK_MODULES(LIBVIEW, gtk+-$GTK_API_VERSION >= $GTK_REQUIRED gail$GAIL_API_VERSION >= $GTK_REQUIRED gthread-2.0 gio-2.0 >= $GLIB_REQUIRED) +PKG_CHECK_MODULES(BACKEND, cairo >= $CAIRO_REQUIRED gtk+-$GTK_API_VERSION >= $GTK_REQUIRED) +PKG_CHECK_MODULES(FRONTEND_CORE, gtk+-$GTK_API_VERSION >= $GTK_REQUIRED gthread-2.0 gio-2.0 >= $GLIB_REQUIRED) + +SHELL_PLATFORM_PKGS= +case "$with_platform" in + hildon) AC_DEFINE([PLATFORM_HILDON],[1],[Define if building for the hildon platform]) + SHELL_PLATFORM_PKGS="hildon-1 hildon-fm-2 libosso" + ;; + mate) + # Evince has a rather soft run-time dependency on hicolor-icon-theme. + # If the hicolor theme is not available, Evince fails to display some + # icons. Because we cannot check for it at run-time, we instead + # would like to require the icon theme at compile-time. But, because + # the hicolor-icon-theme does not have a pkgconfig file, on mate we + # require the mate icon theme instead. + SHELL_PLATFORM_PKGS="mate-icon-theme >= $MATE_ICON_THEME_REQUIRED" + # The totem-screensaver and egg_smclient code use x11 directly. + SHELL_PLATFORM_PKGS="$SHELL_PLATFORM_PKGS x11" + ;; + *) + # On all other platforms we issue a warning about the runtime + # dependency. + AC_MSG_WARN([Evince has a soft run-time dependency on hicolor-icon-theme. You are advised to have this theme installed when running Evince.]); + SHELL_PLATFORM_PKGS="" + ;; +esac + +PKG_CHECK_MODULES([SHELL_CORE],[libxml-2.0 >= $LIBXML_REQUIRED gtk+-$GTK_API_VERSION >= $GTK_REQUIRED gio-2.0 >= $GLIB_REQUIRED gthread-2.0 $SHELL_PLATFORM_PKGS]) + +# ********* +# SM client +# ********* + +GDK_TARGET="$($PKG_CONFIG --variable target gdk-$GTK_API_VERSION)" + +AC_MSG_CHECKING([which smclient backend to use]) +AC_ARG_WITH([smclient], + [AS_HELP_STRING([--with-smclient-backend=no|xsmp|win32|quartz], + [Setting smclient backend (default:auto)])], + [], + [case "$GDK_TARGET" in + x11) case "$with_platform" in + mate) with_smclient=xsmp ;; + *) with_smclient=no ;; + esac ;; + win32|quartz) with_smclient=$GDK_TARGET ;; + *) with_smclient=no ;; + esac]) + +AC_MSG_RESULT([$with_smclient]) + +if test "$with_smclient" != "no"; then + AC_DEFINE([WITH_SMCLIENT],[1],[Define if smclient is enabled]) + + case "$with_smclient" in + xsmp) SMCLIENT_PKGS="sm >= 1.0.0" ;; + *) SMCLIENT_PKGS="" ;; + esac + + PKG_CHECK_MODULES([SMCLIENT],[gtk+-$GTK_API_VERSION gthread-2.0 $SMCLIENT_PKGS]) + AC_SUBST([SMCLIENT_CFLAGS]) + AC_SUBST([SMCLIENT_LIBS]) +fi + +AM_CONDITIONAL([WITH_SMCLIENT],[test "$with_smclient" != "no"]) +AM_CONDITIONAL([WITH_SMCLIENT_XSMP],[test "$with_smclient" = "xsmp"]) +AM_CONDITIONAL([WITH_SMCLIENT_WIN32],[test "$with_smclient" = "win32"]) +AM_CONDITIONAL([WITH_SMCLIENT_QUARTZ],[test "$with_smclient" = "quartz"]) + +# *** + +BACKEND_LIBTOOL_FLAGS="-module -avoid-version -no-undefined -export-symbols \$(top_srcdir)/backend/backend.symbols" +AC_SUBST(BACKEND_LIBTOOL_FLAGS) + +dnl ===== Check special functions +evince_save_LIBS=$LIBS +LIBS="$LIBS $BACKEND_LIBS" +AC_CHECK_FUNCS(cairo_format_stride_for_width) +LIBS=$evince_save_LIBS + +# ****************** +# GKT+ Unix Printing +# ****************** + +AC_MSG_CHECKING([whether gtk+-unix-print support is requested]) +AC_ARG_WITH([gtk-unix-print], + [AS_HELP_STRING([--without-gtk-unix-print], + [Disable the use of gtk-unix-print])], + [],[case "$os_win32" in + yes) with_gtk_unix_print=no ;; + *) with_gtk_unix_print=yes ;; + esac]) + +AC_MSG_RESULT([$with_gtk_unix_print]) + +if test "$with_gtk_unix_print" = "yes"; then + PKG_CHECK_MODULES(GTKUNIXPRINT, [gtk+-unix-print-$GTK_API_VERSION >= $GTK_REQUIRED]) + AC_DEFINE([GTKUNIXPRINT_ENABLED], [1], [Define if gtk+-unix-print is enabled.]) +fi + +# ********************* +# MATE Keyring support +# ********************* + +AC_ARG_WITH(keyring, + [AS_HELP_STRING([--without-keyring], + [Disable the use of mate-keyring])], + [], + [case "$with_platform" in + mate) with_keyring=yes ;; + hildon|win32) with_keyring=no ;; + esac]) + +AM_CONDITIONAL([WITH_KEYRING],[test "$with_keyring" = "yes"]) + +if test "$with_keyring" = "yes"; then + PKG_CHECK_MODULES(KEYRING, mate-keyring-1 >= $KEYRING_REQUIRED) + AC_DEFINE([WITH_KEYRING],[1],[Define if KEYRING support is enabled]) +fi + +# **** +# DBUS +# **** + +AC_ARG_ENABLE([dbus], + [AS_HELP_STRING([--disable-dbus], [Disable support for dbus])], + [], + [case "$with_platform" in + mate) enable_dbus=yes ;; + hildon) enable_dbus=no ;; + esac]) + +if test "$enable_dbus" = "yes"; then + AC_DEFINE([ENABLE_DBUS],[1],[Define if DBUS support is enabled]) + + PKG_CHECK_MODULES([EV_DAEMON], [gio-2.0 >= $GLIB_REQUIRED]) +fi + +AM_CONDITIONAL([ENABLE_DBUS], [test "$enable_dbus" = "yes"]) + +dnl ========= Check for MateConf + +AC_MSG_CHECKING([whether MateConf support is requested]) +AC_ARG_WITH([mateconf], + [AS_HELP_STRING([--without-mateconf], + [Disable the use of mateconf])], + [], + [case "$os_win32" in + yes) with_mateconf=no ;; + *) with_mateconf=yes ;; + esac]) +AC_MSG_RESULT([$with_mateconf]) + +AM_CONDITIONAL([WITH_MATECONF],[test "$with_mateconf" = "yes"]) + +if test "$with_mateconf" = "yes"; then + PKG_CHECK_MODULES([MATECONF],[mateconf-2.0]) + AC_DEFINE([WITH_MATECONF],[1],[Define if MateConf support is enabled]) + + AM_MATECONF_SOURCE_2 + + AC_PATH_PROG([MATECONFTOOL], [mateconftool-2], [false]) + if test "$MATECONFTOOL" = "false"; then + AC_MSG_ERROR([mateconftool-2 executable not found in your path - should be installed with MateConf]) + fi +else + AM_CONDITIONAL([MATECONF_SCHEMAS_INSTALL],false) +fi + +dnl Debug mode + +AC_ARG_ENABLE([debug], + AS_HELP_STRING([--enable-debug], + [Turn on evince debug mode]), + [enable_debug=$enableval], + [enable_debug=no]) + +if test "x$enable_debug" = "xyes"; then + DEBUG_FLAGS="-DEV_ENABLE_DEBUG" +fi + +AC_ARG_ENABLE([tests], + AS_HELP_STRING([--enable-tests], [Enable the tests]), + [enable_tests=$enableval], + [enable_tests=yes]) + +AM_CONDITIONAL(ENABLE_TESTS, test x$enable_tests = xyes) + +LIBDOCUMENT_CFLAGS="$LIBDOCUMENT_CFLAGS $DEBUG_FLAGS" +LIBDOCUMENT_LIBS="$LIBDOCUMENT_LIBS" +AC_SUBST(LIBDOCUMENT_CFLAGS) +AC_SUBST(LIBDOCUMENT_LIBS) + +LIBVIEW_CFLAGS="$LIBVIEW_CFLAGS $GTKUNIXPRINT_CFLAGS $DEBUG_FLAGS" +LIBVIEW_LIBS="$LIBVIEW_LIBS $GTKUNIXPRINT_LIBS -lm" +AC_SUBST(LIBVIEW_CFLAGS) +AC_SUBST(LIBVIEW_LIBS) + +BACKEND_CFLAGS="$BACKEND_CFLAGS -DGDK_MULTIHEAD_SAFE -DGTK_MULTIHEAD_SAFE $DEBUG_FLAGS" +BACKEND_LIBS="$BACKEND_LIBS -lm" +AC_SUBST(BACKEND_CFLAGS) +AC_SUBST(BACKEND_LIBS) + +SHELL_CFLAGS="$SHELL_CORE_CFLAGS $KEYRING_CFLAGS $MATECONF_CFLAGS -DGDK_MULTIHEAD_SAFE -DGTK_MULTIHEAD_SAFE $DEBUG_FLAGS" +SHELL_LIBS="$SHELL_CORE_LIBS $KEYRING_LIBS $MATECONF_LIBS -lz -lm" +AC_SUBST(SHELL_CFLAGS) +AC_SUBST(SHELL_LIBS) + +FRONTEND_CFLAGS="$FRONTEND_CORE_CFLAGS $DEBUG_FLAGS" +FRONTEND_LIBS="$FRONTEND_CORE_LIBS -lz" +AC_SUBST(FRONTEND_CFLAGS) +AC_SUBST(FRONTEND_LIBS) + +EV_DAEMON_CFLAGS="$EV_DAEMON_CFLAGS $DEBUG_FLAGS" +AC_SUBST([EV_DAEMON_CFLAGS]) +AC_SUBST([EV_DAEMON_LIBS]) + +# Check for Caja property page build +AC_ARG_ENABLE([caja], + [AS_HELP_STRING([--disable-caja], + [Disable build of caja extensions])], + [], + [case "$with_platform" in + mate) enable_caja=yes ;; + *) enable_caja=no ;; + esac]) + +if test "$enable_caja" = "yes" ; then + PKG_CHECK_MODULES([CAJA],[gtk+-x11-$GTK_API_VERSION $MM gthread-2.0 libcaja-extension], + [],[AC_MSG_ERROR([libcaja-extension not found; use --disable-caja to disable the caja extensions])]) + CAJA_EXTENSION_DIR=`$PKG_CONFIG --variable=extensiondir libcaja-extension` + AC_SUBST([cajaextensiondir],[$CAJA_EXTENSION_DIR]) + AC_SUBST(CAJA_CFLAGS) + AC_SUBST(CAJA_LIBS) + + AC_DEFINE([HAVE_CAJA],[1], [defined if you build the caja plugin]) +fi + +AM_CONDITIONAL([ENABLE_CAJA],[test "$enable_caja" = "yes"]) + +# Check for thumbnailer build + +AC_ARG_ENABLE([thumbnailer], + [AS_HELP_STRING([--disable-thumbnailer], + [Disable MATE thumbnailer])], + [], + [case "$with_platform" in + hildon) enable_thumbnailer=no ;; + *) enable_thumbnailer=yes ;; + esac]) + +AM_CONDITIONAL([ENABLE_THUMBNAILER],[test "$enable_thumbnailer" = "yes"]) + +# *************** +# Print Previewer +# *************** + +AC_ARG_ENABLE([previewer], + [AS_HELP_STRING([--disable-previewer], + [Disable the MATE Document Previewer])], + [], + [case "$with_platform" in + hildon) enable_previewer=no ;; + *) enable_previewer=yes ;; + esac]) + +if test x$enable_previewer = "xyes" ; then + PKG_CHECK_MODULES([PREVIEWER],[gtk+-$GTK_API_VERSION >= $GTK_REQUIRED gthread-2.0 gio-2.0 >= $GLIB_REQUIRED]) +fi + +AM_CONDITIONAL([ENABLE_PREVIEWER],[test "$enable_previewer" = "yes"]) +PREVIEWER_CFLAGS="$PREVIEWER_CFLAGS $GTKUNIXPRINT_CFLAGS $DEBUG_FLAGS" +PREVIEWER_LIBS="$PREVIEWER_LIBS $GTKUNIXPRINT_LIBS -lz" +AC_SUBST(PREVIEWER_CFLAGS) +AC_SUBST(PREVIEWER_LIBS) + +# *** +# GIR +# *** + +AC_MSG_CHECKING([whether GObject introspection is requested]) +AC_ARG_ENABLE([introspection], + AS_HELP_STRING([--enable-introspection], + [Enable GObject introspection]), + [enable_introspection=$enableval], + [enable_introspection=no]) +AC_MSG_RESULT([$enable_introspection]) + +G_IR_SCANNER= +G_IR_COMPILER= +G_IR_GENERATE= +GIRDIR= +GIRTYPELIBDIR= + +if test "$enable_introspection" = "yes"; then + GOBJECT_INTROSPECTION_REQUIRED=0.6 + PKG_CHECK_MODULES([GOBJECT_INTROSPECTION],[gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_REQUIRED]) + + G_IR_SCANNER="$($PKG_CONFIG --variable=g_ir_scanner gobject-introspection-1.0)" + G_IR_COMPILER="$($PKG_CONFIG --variable=g_ir_compiler gobject-introspection-1.0)" + G_IR_GENERATE="$($PKG_CONFIG --variable=g_ir_generate gobject-introspection-1.0)" + GIRDIR="$($PKG_CONFIG --variable=girdir gobject-introspection-1.0)" + GIRTYPELIBDIR="$($PKG_CONFIG --variable=typelibdir gobject-introspection-1.0)" +fi + +AC_SUBST([G_IR_SCANNER]) +AC_SUBST([G_IR_COMPILER]) +AC_SUBST([G_IR_GENERATE]) +AC_SUBST([GIRDIR]) +AC_SUBST([GIRTYPELIBDIR]) + +AM_CONDITIONAL([ENABLE_INTROSPECTION],[test "$enable_introspection" = "yes"]) + +dnl ================== portability checks =========================================== + +dnl for backtrace() +AC_CHECK_HEADERS([execinfo.h]) + +AC_CHECK_DECL([_NL_MEASUREMENT_MEASUREMENT],[ + AC_DEFINE([HAVE__NL_MEASUREMENT_MEASUREMENT],[1],[Define if _NL_MEASUREMENT_MEASUREMENT is available]) + ],[],[#include ]) + +dnl ================== pdf checks =================================================== +AC_ARG_ENABLE([pdf], + [AS_HELP_STRING([--disable-pdf], + [Disable the PDF support])], + [enable_pdf=$enableval], + [enable_pdf=yes]) + +if test "x$enable_pdf" = "xyes"; then + POPPLER_REQUIRED=0.14.0 + PKG_CHECK_MODULES(POPPLER, poppler-glib >= $POPPLER_REQUIRED libxml-2.0 >= $LIBXML_REQUIRED,enable_pdf=yes,enable_pdf=no) + + if test "x$enable_pdf" = "xyes"; then + evince_save_LIBS=$LIBS + LIBS="$LIBS $POPPLER_LIBS" + AC_CHECK_FUNCS(poppler_page_get_text_layout) + AC_CHECK_FUNCS(poppler_page_get_selected_text) + AC_CHECK_FUNCS(poppler_page_add_annot) + LIBS=$evince_save_LIBS + PKG_CHECK_MODULES(CAIRO_PDF, cairo-pdf, enable_cairo_pdf=yes, enable_cairo_pdf=no) + if test x$enable_cairo_pdf = xyes; then + AC_DEFINE([HAVE_CAIRO_PDF], [1], [defined if cairo-pdf is available]) + fi + + PKG_CHECK_MODULES(CAIRO_PS, cairo-ps, enable_cairo_ps=yes, enable_cairo_ps=no) + if test x$enable_cairo_ps = xyes; then + AC_DEFINE([HAVE_CAIRO_PS], [1], [defined if cairo-ps is available]) + fi + else + AC_MSG_ERROR("PDF support is disabled since poppler-glib library version $POPPLER_REQUIRED or newer not found") + fi +fi + +AM_CONDITIONAL(ENABLE_PDF, test x$enable_pdf = xyes) +dnl ================== end of pdf checks ============================================ + +dnl libspectre (used by ps and dvi backends) +SPECTRE_REQUIRED=0.2.0 +PKG_CHECK_MODULES(SPECTRE, libspectre >= $SPECTRE_REQUIRED,have_spectre=yes,have_spectre=no) +AM_CONDITIONAL(HAVE_SPECTRE, test x$have_spectre = xyes) +if test "x$have_spectre" = "xyes"; then + AC_DEFINE([HAVE_SPECTRE], [1], [Have libspectre]) +fi + +dnl ================== ps checks ==================================================== +AC_ARG_ENABLE(ps, + [AS_HELP_STRING([--disable-ps], + [Disable the PostScript backend])], + [enable_ps=$enableval], + [enable_ps=yes]) + +if test "x$enable_ps" = "xyes"; then + if test "x$have_spectre" = "xyes"; then + AC_DEFINE([ENABLE_PS], [1], [Enable support for PostScript files.]) + else + enable_ps="no" + AC_MSG_WARN([PS support is disabled since libspectre (version >= $SPECTRE_REQUIRED) is needed]) + fi +fi +AM_CONDITIONAL(ENABLE_PS, test x$enable_ps = xyes) +dnl ======================== End of ps checks =================================== + +dnl ================== tiff checks =================================================== +AC_ARG_ENABLE(tiff, + [AS_HELP_STRING([--disable-tiff], + [Disable the support of multipage tiff])], + [enable_tiff=$enableval], + [enable_tiff=yes]) + +if test "x$enable_tiff" = "xyes"; then + AC_CHECK_HEADERS([tiff.h],enable_tiff=yes,enable_tiff=no,) + if test "x$enable_tiff" = "xyes"; then + AC_CHECK_LIB([tiff],TIFFOpen,enable_tiff=yes,enable_tiff=no,"-lz") + AC_CHECK_LIB([tiff],TIFFReadRGBAImageOriented,enable_tiff=yes,enable_tiff=no,"-lz") + fi + if test "x$enable_tiff" = "xyes"; then + AC_DEFINE([ENABLE_TIFF], [1], [Enable multipage tiff support.]) + else + AC_MSG_WARN("Tiff support is disabled since tiff library version 3.6 or newer not found") + fi +fi + +AM_CONDITIONAL(ENABLE_TIFF, test x$enable_tiff = xyes) +dnl ================== end of tiff checks ============================================ + +dnl ================== djvu checks =================================================== + +AC_ARG_ENABLE(djvu, + [AS_HELP_STRING([--disable-djvu], + [Disable the support of djvu viewer])], + [enable_djvu=$enableval], + [enable_djvu=yes]) + +if test "x$enable_djvu" = "xyes"; then + DJVULIBRE_REQUIRED=3.5.17 + PKG_CHECK_MODULES(DJVU, ddjvuapi >= $DJVULIBRE_REQUIRED, enable_djvu=yes, enable_djvu=no) + + if test "x$enable_djvu" = "xyes"; then + AC_DEFINE([ENABLE_DJVU], [1], [Enable djvu viewer support.]) + else + AC_MSG_WARN([ +** Djvu support is disabled since a recent version of the djvulibre +** library was not found. You need at least djvulibre-3.5.17 which +** can be found on http://djvulibre.djvuzone.org +]) + fi +fi + +AM_CONDITIONAL(ENABLE_DJVU, test x$enable_djvu = xyes) + +dnl ================== End of djvu checks =================================================== + +dnl ================== dvi checks =================================================== + +AC_ARG_ENABLE(dvi, + [AS_HELP_STRING([--disable-dvi], + [Disable the support of dvi viewer])], + [enable_dvi=$enableval], + [enable_dvi=yes]) + +AC_ARG_ENABLE(t1lib, + [AS_HELP_STRING([--enable-t1lib], + [Compile with support of t1lib for type1 fonts in dvi])], + [enable_type1_fonts=$enableval], + [enable_type1_fonts=no]) + +if test "x$enable_dvi" = "xyes"; then + AC_C_CONST + AC_C_INLINE + AC_TYPE_SIZE_T + AC_CHECK_SIZEOF(long, 4) + AC_CHECK_SIZEOF(int, 4) + AC_CHECK_SIZEOF(short, 2) + AC_CHECK_SIZEOF(void *, 4) + AC_CHECK_LIB([kpathsea],[kpse_init_prog],[enable_dvi=yes],[enable_dvi=no]) + + if test "x$enable_dvi" = "xyes"; then + AC_DEFINE([ENABLE_DVI], [1], [Enable dvi viewer support.]) + else + AC_MSG_WARN("Dvi support is disabled since kpathsea library is not found. Check your TeX installation.") + fi +fi +AM_CONDITIONAL(ENABLE_DVI, test x$enable_dvi = xyes) + +if test "x$enable_dvi" = "xyes"; then + if test "x$enable_type1_fonts" = "xyes"; then + AC_CHECK_LIB([t1],T1_InitLib,enable_type1_fonts=yes,enable_type1_fonts=no,[-lm]) + fi + + if test "x$enable_type1_fonts" = xyes; then + AC_DEFINE([WITH_TYPE1_FONTS], [1], [Enable t1lib support in dvi.]) + fi +else + enable_type1_fonts=no +fi +AM_CONDITIONAL(WITH_TYPE1_FONTS, test x$enable_type1_fonts = xyes) + +dnl ================== End of dvi checks =================================================== + +dnl ================== pixbuf checks =================================================== + +AC_ARG_ENABLE(pixbuf, + [AS_HELP_STRING([--enable-pixbuf], + [Compile with support of pixbuf])], + [enable_pixbuf=$enableval], + [enable_pixbuf=no]) + +if test "x$enable_pixbuf" = "xyes"; then + AC_DEFINE([ENABLE_PIXBUF], [1], [Enable pixbuf support.]) +fi + +AM_CONDITIONAL(ENABLE_PIXBUF, test x$enable_pixbuf = xyes) + +dnl ================== End of pixbuf checks =================================================== + +dnl ================== comic book checks =================================================== + +AC_ARG_ENABLE(comics, + [AS_HELP_STRING([--enable-comics], + [Compile with support for comic book archives])], + [enable_comics=$enableval], + [enable_comics=yes]) + +if test "x$enable_comics" = "xyes"; then + AC_DEFINE([ENABLE_COMICS], [1], [Enable support for comics.]) +fi +AM_CONDITIONAL(ENABLE_COMICS, test x$enable_comics = xyes) + +dnl ================== End of comic book checks ============================================ + +dnl ================== impress book checks =================================================== + +AC_ARG_ENABLE(impress, + [AS_HELP_STRING([--enable-impress], + [Compile with support for impress presentations])], + [enable_impress=$enableval], + [enable_impress=no]) + +if test "x$enable_impress" = "xyes"; then + AC_DEFINE([ENABLE_IMPRESS], [1], [Enable support for impress.]) +fi +AM_CONDITIONAL(ENABLE_IMPRESS, test x$enable_impress = xyes) + +dnl ================== End of impress book checks ============================================ + +dnl =================== Mime types list ==================================================== + +if test "x$enable_pdf" = "xyes" ; then + EVINCE_MIME_TYPES="application/pdf;application/x-bzpdf;application/x-gzpdf;" +fi +if test "x$enable_ps" = "xyes" ; then + EVINCE_MIME_TYPES="${EVINCE_MIME_TYPES}application/postscript;application/x-bzpostscript;application/x-gzpostscript;image/x-eps;image/x-bzeps;image/x-gzeps;" +fi +if test "x$enable_dvi" = "xyes"; then + EVINCE_MIME_TYPES="${EVINCE_MIME_TYPES}application/x-dvi;application/x-bzdvi;application/x-gzdvi;" +fi +if test "x$enable_djvu" = "xyes"; then + EVINCE_MIME_TYPES="${EVINCE_MIME_TYPES}image/vnd.djvu;" +fi +if test "x$enable_tiff" = "xyes"; then + EVINCE_MIME_TYPES="${EVINCE_MIME_TYPES}image/tiff;" +fi +if test "x$enable_comics" = "xyes"; then + EVINCE_MIME_TYPES="${EVINCE_MIME_TYPES}application/x-cbr;application/x-cbz;application/x-cb7;application/x-cbt;" +fi +if test "x$enable_pixbuf" = "xyes"; then + EVINCE_MIME_TYPES="${EVINCE_MIME_TYPES}image/*;" +fi +if test "x$enable_impress" = "xyes"; then + EVINCE_MIME_TYPES="${EVINCE_MIME_TYPES}application/vnd.sun.xml.impress;application/vnd.oasis.opendocument.presentation;" +fi +AC_SUBST(EVINCE_MIME_TYPES) + +AC_CHECK_FUNC(localtime_r, AC_DEFINE(HAVE_LOCALTIME_R, 1, [Defines if localtime_r is available on your system])) + +# ***************** +# Help files +# ***************** + +MATE_DOC_INIT([], mate_doc_utils=yes, mate_doc_utils=no) + +AC_ARG_ENABLE(help, + [AS_HELP_STRING([--disable-help], [Disable offline help files])], + [enable_help=$enableval], + [enable_help=yes]) + +if test "x$enable_help" = "xyes" && test "x$os_win32" = "xyes" ; then + AC_MSG_WARN([Offline help is disabled (not implemented on Windows).]) + enable_help=no +fi + +if test "x$enable_help" = "xyes" && test "x$mate_doc_utils" == "xno"; then + AC_MSG_ERROR( + [mate-doc-utils not found; use --disable-help to disable help files] + ) +fi + +if test "x$enable_help" = "xyes"; then + AC_DEFINE([OFFLINE_HELP_ENABLED], [1], [Enable offline help]) +fi + +AM_CONDITIONAL(ENABLE_HELP, test "x$enable_help" = "xyes") + +# ***************** +# API documentation +# ***************** + +GTK_DOC_CHECK([1.13],[--flavour no-tmpl]) + +AC_SUBST([GLIB_PREFIX],[$($PKG_CONFIG --variable=prefix glib-2.0)]) +AC_SUBST([GTK_PREFIX],[$($PKG_CONFIG --variable=prefix gtk+-$GTK_API_VERSION)]) + +# ****************** +# Backends directory +# ****************** + +AC_SUBST([backenddir],"\$(libdir)/evince/ev_binary_version/backends") +AC_SUBST([backend_binary_version],"ev_binary_version") + +# ********** +# Versioning +# ********** + +AC_SUBST([EV_MAJOR_VERSION],[ev_major_version]) +AC_SUBST([EV_MINOR_VERSION],[ev_minor_version]) +AC_SUBST([EV_MICRO_VERSION],[ev_micro_version]) + +AC_SUBST([EV_API_VERSION],[ev_api_version]) +AC_SUBST([EV_API_VERSION_U],[AS_TR_SH([ev_api_version])]) +AC_SUBST([EV_BINARY_VERSION],[ev_binary_version]) + +AC_SUBST([EV_DOCUMENT_LT_VERSION_INFO],[ev_document_lt_version_info]) +AC_SUBST([EV_DOCUMENT_LT_CURRENT_MINUS_AGE],[ev_document_lt_current_minus_age]) +AC_SUBST([EV_VIEW_LT_VERSION_INFO],[ev_view_lt_version_info]) +AC_SUBST([EV_VIEW_LT_CURRENT_MINUS_AGE],[ev_view_lt_current_minus_age]) + +# ***************************************************************************** +# ***************************************************************************** + +AC_CONFIG_FILES([ +backend/Makefile +backend/comics/Makefile +backend/djvu/Makefile +backend/dvi/Makefile +backend/dvi/mdvi-lib/Makefile +backend/impress/Makefile +backend/pdf/Makefile +backend/pixbuf/Makefile +backend/ps/Makefile +backend/tiff/Makefile +cut-n-paste/Makefile +cut-n-paste/gimpcellrenderertoggle/Makefile +cut-n-paste/smclient/Makefile +cut-n-paste/toolbar-editor/Makefile +cut-n-paste/zoom-control/Makefile +cut-n-paste/totem-screensaver/Makefile +cut-n-paste/synctex/Makefile +data/evince.desktop.in +data/Makefile +data/icons/Makefile +data/icons/16x16/Makefile +data/icons/16x16/apps/Makefile +data/icons/16x16/actions/Makefile +data/icons/16x16/mimetypes/Makefile +data/icons/22x22/Makefile +data/icons/22x22/apps/Makefile +data/icons/22x22/actions/Makefile +data/icons/22x22/mimetypes/Makefile +data/icons/24x24/Makefile +data/icons/24x24/apps/Makefile +data/icons/24x24/actions/Makefile +data/icons/24x24/mimetypes/Makefile +data/icons/32x32/Makefile +data/icons/32x32/actions/Makefile +data/icons/32x32/mimetypes/Makefile +data/icons/48x48/Makefile +data/icons/48x48/apps/Makefile +data/icons/48x48/actions/Makefile +data/icons/scalable/Makefile +data/icons/scalable/apps/Makefile +data/icons/scalable/actions/Makefile +data/icons/scalable/mimetypes/Makefile +help/Makefile +help/reference/Makefile +help/reference/libdocument/Makefile +help/reference/libdocument/version.xml +help/reference/libview/Makefile +help/reference/libview/version.xml +help/reference/shell/Makefile +help/reference/shell/version.xml +libdocument/Makefile +libdocument/ev-version.h +libmisc/Makefile +libview/Makefile +Makefile +po/Makefile.in +previewer/Makefile +properties/Makefile +shell/Makefile +test/Makefile +thumbnailer/Makefile +]) + +AC_CONFIG_FILES(evince-document-[]ev_api_version[].pc:evince-document.pc.in) +AC_CONFIG_FILES(evince-view-[]ev_api_version[].pc:evince-view.pc.in) + +AC_OUTPUT + +echo " +Configure summary: + Platform...........: $with_platform + GTK+ version.......: $with_gtk + MateConf Support......: $with_mateconf + GTK+ Unix Print....: $with_gtk_unix_print + Keyring Support....: $with_keyring + DBUS Support.......: $enable_dbus + SM client support..: $with_smclient + Help files.........: $enable_help + Caja Plugin....: $enable_caja + Thumbnailer........: $enable_thumbnailer + Previewer..........: $enable_previewer + Gtk-Doc Support....: $enable_gtk_doc + Debug mode.........: $enable_debug + GObj. Introspection: $enable_introspection + Tests..............: $enable_tests + + PDF Backend........: $enable_pdf + PostScript Backend.: $enable_ps + TIFF Backend.......: $enable_tiff + DJVU Backend.......: $enable_djvu + DVI Backend........: $enable_dvi + Pixbuf Backend.....: $enable_pixbuf + Comics Backend.....: $enable_comics + Impress Backend....: $enable_impress +" diff --git a/cut-n-paste/Makefile.am b/cut-n-paste/Makefile.am new file mode 100644 index 00000000..b53b9b9e --- /dev/null +++ b/cut-n-paste/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = zoom-control toolbar-editor totem-screensaver smclient gimpcellrenderertoggle synctex + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/gimpcellrenderertoggle/Makefile.am b/cut-n-paste/gimpcellrenderertoggle/Makefile.am new file mode 100644 index 00000000..fffeffa7 --- /dev/null +++ b/cut-n-paste/gimpcellrenderertoggle/Makefile.am @@ -0,0 +1,38 @@ +noinst_LTLIBRARIES = libgimpcellrenderertoggle.la + +libgimpcellrenderertoggle_la_sources = \ + gimpcellrenderertoggle.h \ + gimpcellrenderertoggle.c + +libgimpcellrenderertoggle_la_CFLAGS = \ + $(SHELL_CORE_CFLAGS) \ + $(WARNING_CFLAGS) \ + $(DISABLE_DEPRECATED) + +libgimpcellrenderertoggle_la_built_sources = \ + gimpwidgetsmarshal.h \ + gimpwidgetsmarshal.c + +libgimpcellrenderertoggle_la_SOURCES = \ + $(libgimpcellrenderertoggle_la_built_sources) \ + $(libgimpcellrenderertoggle_la_sources) + +libgimpcellrenderertoggle_la_extra_sources = gimpwidgetsmarshal.list + +gimpwidgetsmarshal.h: $(srcdir)/gimpwidgetsmarshal.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=_gimp_widgets_marshal $(srcdir)/gimpwidgetsmarshal.list --header >> xgen-wmh \ + && (cmp -s xgen-wmh $(@F) || cp xgen-wmh $(@F)) \ + && rm -f xgen-wmh xgen-wmh~ + +gimpwidgetsmarshal.c: gimpwidgetsmarshal.h + $(AM_V_GEN)echo "#include \"gimpwidgetsmarshal.h\"" >> xgen-wmc \ + && $(GLIB_GENMARSHAL) --prefix=_gimp_widgets_marshal $(srcdir)/gimpwidgetsmarshal.list --body >> xgen-wmc \ + && cp xgen-wmc $(@F) \ + && rm -f xgen-wmc xgen-wmc~ + +gen_sources = xgen-wmh xgen-wmc $(libgimpcellrenderertoggle_la_built_sources) +CLEANFILES = $(gen_sources) + +EXTRA_DIST = $(libgimpcellrenderertoggle_la_extra_sources) + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.c b/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.c new file mode 100644 index 00000000..e9a25f15 --- /dev/null +++ b/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.c @@ -0,0 +1,492 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcellrenderertoggle.c + * Copyright (C) 2003-2004 Sven Neumann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#include "gimpwidgetsmarshal.h" +#include "gimpcellrenderertoggle.h" + + +#define DEFAULT_ICON_SIZE GTK_ICON_SIZE_BUTTON + + +enum +{ + CLICKED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_STOCK_ID, + PROP_STOCK_SIZE +}; + + +static void gimp_cell_renderer_toggle_finalize (GObject *object); +static void gimp_cell_renderer_toggle_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_toggle_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_toggle_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *rectangle, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); +static void gimp_cell_renderer_toggle_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags); +static gboolean gimp_cell_renderer_toggle_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags); +static void gimp_cell_renderer_toggle_create_pixbuf (GimpCellRendererToggle *toggle, + GtkWidget *widget); + + +G_DEFINE_TYPE (GimpCellRendererToggle, gimp_cell_renderer_toggle, + GTK_TYPE_CELL_RENDERER_TOGGLE) + +#define parent_class gimp_cell_renderer_toggle_parent_class + +static guint toggle_cell_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_cell_renderer_toggle_class_init (GimpCellRendererToggleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + + toggle_cell_signals[CLICKED] = + g_signal_new ("clicked", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpCellRendererToggleClass, clicked), + NULL, NULL, + _gimp_widgets_marshal_VOID__STRING_FLAGS, + G_TYPE_NONE, 2, + G_TYPE_STRING, + GDK_TYPE_MODIFIER_TYPE); + + object_class->finalize = gimp_cell_renderer_toggle_finalize; + object_class->get_property = gimp_cell_renderer_toggle_get_property; + object_class->set_property = gimp_cell_renderer_toggle_set_property; + + cell_class->get_size = gimp_cell_renderer_toggle_get_size; + cell_class->render = gimp_cell_renderer_toggle_render; + cell_class->activate = gimp_cell_renderer_toggle_activate; + + g_object_class_install_property (object_class, + PROP_STOCK_ID, + g_param_spec_string ("stock-id", + NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_STOCK_SIZE, + g_param_spec_int ("stock-size", + NULL, NULL, + 0, G_MAXINT, + DEFAULT_ICON_SIZE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_cell_renderer_toggle_init (GimpCellRendererToggle *toggle) +{ +} + +static void +gimp_cell_renderer_toggle_finalize (GObject *object) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (object); + + if (toggle->stock_id) + { + g_free (toggle->stock_id); + toggle->stock_id = NULL; + } + + if (toggle->pixbuf) + { + g_object_unref (toggle->pixbuf); + toggle->pixbuf = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_cell_renderer_toggle_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (object); + + switch (param_id) + { + case PROP_STOCK_ID: + g_value_set_string (value, toggle->stock_id); + break; + case PROP_STOCK_SIZE: + g_value_set_int (value, toggle->stock_size); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gimp_cell_renderer_toggle_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (object); + + switch (param_id) + { + case PROP_STOCK_ID: + if (toggle->stock_id) + g_free (toggle->stock_id); + toggle->stock_id = g_value_dup_string (value); + break; + case PROP_STOCK_SIZE: + toggle->stock_size = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } + + if (toggle->pixbuf) + { + g_object_unref (toggle->pixbuf); + toggle->pixbuf = NULL; + } +} + +static void +gimp_cell_renderer_toggle_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (cell); + GtkStyle *style = gtk_widget_get_style (widget); + gint calc_width; + gint calc_height; + gint pixbuf_width; + gint pixbuf_height; + gfloat xalign; + gfloat yalign; + gint xpad; + gint ypad; + + if (! toggle->stock_id) + { + GTK_CELL_RENDERER_CLASS (parent_class)->get_size (cell, + widget, + cell_area, + x_offset, y_offset, + width, height); + return; + } + + gtk_cell_renderer_get_alignment (cell, &xalign, &yalign); + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + + if (! toggle->pixbuf) + gimp_cell_renderer_toggle_create_pixbuf (toggle, widget); + + pixbuf_width = gdk_pixbuf_get_width (toggle->pixbuf); + pixbuf_height = gdk_pixbuf_get_height (toggle->pixbuf); + + calc_width = (pixbuf_width + + (gint) xpad * 2 + style->xthickness * 2); + calc_height = (pixbuf_height + + (gint) ypad * 2 + style->ythickness * 2); + + if (width) + *width = calc_width; + + if (height) + *height = calc_height; + + if (cell_area) + { + if (x_offset) + { + *x_offset = (((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ? + (1.0 - xalign) : xalign) * + (cell_area->width - calc_width)); + *x_offset = MAX (*x_offset, 0); + } + + if (y_offset) + { + *y_offset = yalign * (cell_area->height - calc_height); + *y_offset = MAX (*y_offset, 0); + } + } +} + +static void +gimp_cell_renderer_toggle_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + GtkCellRendererState flags) +{ + GimpCellRendererToggle *toggle = GIMP_CELL_RENDERER_TOGGLE (cell); + GtkStyle *style = gtk_widget_get_style (widget); + GdkRectangle toggle_rect; + GdkRectangle draw_rect; + GtkStateType state; + gboolean active; + gint xpad; + gint ypad; + + if (! toggle->stock_id) + { + GTK_CELL_RENDERER_CLASS (parent_class)->render (cell, window, widget, + background_area, + cell_area, expose_area, + flags); + return; + } + + gimp_cell_renderer_toggle_get_size (cell, widget, cell_area, + &toggle_rect.x, + &toggle_rect.y, + &toggle_rect.width, + &toggle_rect.height); + + gtk_cell_renderer_get_padding (cell, &xpad, &ypad); + toggle_rect.x += cell_area->x + xpad; + toggle_rect.y += cell_area->y + ypad; + toggle_rect.width -= xpad * 2; + toggle_rect.height -= ypad * 2; + + if (toggle_rect.width <= 0 || toggle_rect.height <= 0) + return; + + active = + gtk_cell_renderer_toggle_get_active (GTK_CELL_RENDERER_TOGGLE (cell)); + + if (!gtk_cell_renderer_get_sensitive (cell)) + { + state = GTK_STATE_INSENSITIVE; + } + else if ((flags & GTK_CELL_RENDERER_SELECTED) == GTK_CELL_RENDERER_SELECTED) + { + if (gtk_widget_has_focus (widget)) + state = GTK_STATE_SELECTED; + else + state = GTK_STATE_ACTIVE; + } + else + { + if (gtk_cell_renderer_toggle_get_activatable (GTK_CELL_RENDERER_TOGGLE (cell))) + state = GTK_STATE_NORMAL; + else + state = GTK_STATE_INSENSITIVE; + } + + if (gdk_rectangle_intersect (expose_area, cell_area, &draw_rect) && + (flags & GTK_CELL_RENDERER_PRELIT)) + gtk_paint_shadow (style, + window, + state, + active ? GTK_SHADOW_IN : GTK_SHADOW_OUT, + &draw_rect, + widget, NULL, + toggle_rect.x, toggle_rect.y, + toggle_rect.width, toggle_rect.height); + + if (active) + { + GdkPixbuf *insensitive = NULL; + GdkPixbuf *pixbuf = toggle->pixbuf; + + toggle_rect.x += style->xthickness; + toggle_rect.y += style->ythickness; + toggle_rect.width -= style->xthickness * 2; + toggle_rect.height -= style->ythickness * 2; + + if (state == GTK_STATE_INSENSITIVE) + { + GtkIconSource *source; + + source = gtk_icon_source_new (); + gtk_icon_source_set_pixbuf (source, pixbuf); + /* The size here is arbitrary; since size isn't + * wildcarded in the source, it isn't supposed to be + * scaled by the engine function + */ + gtk_icon_source_set_size (source, GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_icon_source_set_size_wildcarded (source, FALSE); + + insensitive = gtk_style_render_icon (gtk_widget_get_style (widget), + source, + gtk_widget_get_direction (widget), + GTK_STATE_INSENSITIVE, + /* arbitrary */ + (GtkIconSize)-1, + widget, + "gimpcellrenderertoggle"); + + gtk_icon_source_free (source); + + pixbuf = insensitive; + } + + if (gdk_rectangle_intersect (&draw_rect, &toggle_rect, &draw_rect)) + { + cairo_t *cr; + + cr = gdk_cairo_create (window); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, toggle_rect.x, toggle_rect.y); + gdk_cairo_rectangle (cr, &draw_rect); + cairo_fill (cr); + + cairo_destroy (cr); + } + + if (insensitive) + g_object_unref (insensitive); + } +} + +static gboolean +gimp_cell_renderer_toggle_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellRendererToggle *toggle = GTK_CELL_RENDERER_TOGGLE (cell); + + if (gtk_cell_renderer_toggle_get_activatable (toggle)) + { + GdkModifierType state = 0; + + GTK_CELL_RENDERER_CLASS (parent_class)->activate (cell, event, widget, + path, background_area, + cell_area, flags); + + if (event && ((GdkEventAny *) event)->type == GDK_BUTTON_PRESS) + state = ((GdkEventButton *) event)->state; + + gimp_cell_renderer_toggle_clicked (GIMP_CELL_RENDERER_TOGGLE (cell), + path, state); + + return TRUE; + } + + return FALSE; +} + +static void +gimp_cell_renderer_toggle_create_pixbuf (GimpCellRendererToggle *toggle, + GtkWidget *widget) +{ + if (toggle->pixbuf) + g_object_unref (toggle->pixbuf); + + toggle->pixbuf = gtk_widget_render_icon (widget, + toggle->stock_id, + toggle->stock_size, NULL); +} + + +/** + * gimp_cell_renderer_toggle_new: + * @stock_id: the stock_id of the icon to use for the active state + * + * Creates a custom version of the #GtkCellRendererToggle. Instead of + * showing the standard toggle button, it shows a stock icon if the + * cell is active and no icon otherwise. This cell renderer is for + * example used in the Layers treeview to indicate and control the + * layer's visibility by showing %GIMP_STOCK_VISIBLE. + * + * Return value: a new #GimpCellRendererToggle + * + * Since: GIMP 2.2 + **/ +GtkCellRenderer * +gimp_cell_renderer_toggle_new (const gchar *stock_id) +{ + return g_object_new (GIMP_TYPE_CELL_RENDERER_TOGGLE, + "stock_id", stock_id, + NULL); +} + +/** + * gimp_cell_renderer_toggle_clicked: + * @cell: a #GimpCellRendererToggle + * @path: + * @state: + * + * Emits the "clicked" signal from a #GimpCellRendererToggle. + * + * Since: GIMP 2.2 + **/ +void +gimp_cell_renderer_toggle_clicked (GimpCellRendererToggle *cell, + const gchar *path, + GdkModifierType state) +{ + g_return_if_fail (GIMP_IS_CELL_RENDERER_TOGGLE (cell)); + g_return_if_fail (path != NULL); + + g_signal_emit (cell, toggle_cell_signals[CLICKED], 0, path, state); +} diff --git a/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.h b/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.h new file mode 100644 index 00000000..6d516d73 --- /dev/null +++ b/cut-n-paste/gimpcellrenderertoggle/gimpcellrenderertoggle.h @@ -0,0 +1,77 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpcellrenderertoggle.h + * Copyright (C) 2003-2004 Sven Neumann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GIMP_CELL_RENDERER_TOGGLE_H__ +#define __GIMP_CELL_RENDERER_TOGGLE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GIMP_TYPE_CELL_RENDERER_TOGGLE (gimp_cell_renderer_toggle_get_type ()) +#define GIMP_CELL_RENDERER_TOGGLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CELL_RENDERER_TOGGLE, GimpCellRendererToggle)) +#define GIMP_CELL_RENDERER_TOGGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CELL_RENDERER_TOGGLE, GimpCellRendererToggleClass)) +#define GIMP_IS_CELL_RENDERER_TOGGLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CELL_RENDERER_TOGGLE)) +#define GIMP_IS_CELL_RENDERER_TOGGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CELL_RENDERER_TOGGLE)) +#define GIMP_CELL_RENDERER_TOGGLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CELL_RENDERER_TOGGLE, GimpCellRendererToggleClass)) + +typedef struct _GimpCellRendererToggle GimpCellRendererToggle; +typedef struct _GimpCellRendererToggleClass GimpCellRendererToggleClass; + +struct _GimpCellRendererToggle +{ + GtkCellRendererToggle parent_instance; + + gchar *stock_id; + GtkIconSize stock_size; + GdkPixbuf *pixbuf; +}; + +struct _GimpCellRendererToggleClass +{ + GtkCellRendererToggleClass parent_class; + + void (* clicked) (GimpCellRendererToggle *cell, + const gchar *path, + GdkModifierType state); + + /* Padding for future expansion */ + void (* _gimp_reserved1) (void); + void (* _gimp_reserved2) (void); + void (* _gimp_reserved3) (void); + void (* _gimp_reserved4) (void); +}; + + +GType gimp_cell_renderer_toggle_get_type (void) G_GNUC_CONST; + +GtkCellRenderer * gimp_cell_renderer_toggle_new (const gchar *stock_id); + +void gimp_cell_renderer_toggle_clicked (GimpCellRendererToggle *cell, + const gchar *path, + GdkModifierType state); + + +G_END_DECLS + +#endif /* __GIMP_CELL_RENDERER_TOGGLE_H__ */ diff --git a/cut-n-paste/gimpcellrenderertoggle/gimpwidgetsmarshal.list b/cut-n-paste/gimpcellrenderertoggle/gimpwidgetsmarshal.list new file mode 100644 index 00000000..f32f0f95 --- /dev/null +++ b/cut-n-paste/gimpcellrenderertoggle/gimpwidgetsmarshal.list @@ -0,0 +1,26 @@ +# see glib-genmarshal(1) for a detailed description of the file format, +# possible parameter types are: +# VOID indicates no return type, or no extra +# parameters. if VOID is used as the parameter +# list, no additional parameters may be present. +# BOOLEAN for boolean types (gboolean) +# CHAR for signed char types (gchar) +# UCHAR for unsigned char types (guchar) +# INT for signed integer types (gint) +# UINT for unsigned integer types (guint) +# LONG for signed long integer types (glong) +# ULONG for unsigned long integer types (gulong) +# ENUM for enumeration types (gint) +# FLAGS for flag enumeration types (guint) +# FLOAT for single-precision float types (gfloat) +# DOUBLE for double-precision float types (gdouble) +# STRING for string types (gchar*) +# BOXED for boxed (anonymous but reference counted) types (GBoxed*) +# POINTER for anonymous pointer types (gpointer) +# PARAM for GParamSpec or derived types (GParamSpec*) +# OBJECT for GObject or derived types (GObject*) +# NONE deprecated alias for VOID +# BOOL deprecated alias for BOOLEAN + +VOID: STRING, FLAGS + diff --git a/cut-n-paste/smclient/Makefile.am b/cut-n-paste/smclient/Makefile.am new file mode 100644 index 00000000..498ed756 --- /dev/null +++ b/cut-n-paste/smclient/Makefile.am @@ -0,0 +1,42 @@ +noinst_LTLIBRARIES = libsmclient.la + +NULL = + +if WITH_SMCLIENT +libsmclient_la_SOURCES = \ + eggsmclient.c \ + eggsmclient.h \ + eggsmclient-private.h \ + $(NULL) + +libsmclient_la_CPPFLAGS = \ + -DG_LOG_DOMAIN=\""EggSMClient"\" \ + $(DISABLE_DEPRECATED) \ + $(AM_CPPFLAGS) + +libsmclient_la_CFLAGS = \ + $(SMCLIENT_CFLAGS) \ + $(WARNING_CFLAGS) \ + $(AM_CFLAGS) + +libsmclient_la_LIBADD = \ + $(SMCLIENT_LIBS) + +if WITH_SMCLIENT_XSMP +libsmclient_la_SOURCES += \ + eggdesktopfile.c \ + eggdesktopfile.h \ + eggsmclient-xsmp.c \ + $(NULL) +libsmclient_la_CPPFLAGS += -DEGG_SM_CLIENT_BACKEND_XSMP +endif +if WITH_SMCLIENT_WIN32 +libsmclient_la_SOURCES += eggsmclient-win32.c +endif +if WITH_SMCLIENT_QUARTZ +libsmclient_la_SOURCES += eggsmclient-osx.c +endif + +endif # WITH_SMCLIENT + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/smclient/eggdesktopfile.c b/cut-n-paste/smclient/eggdesktopfile.c new file mode 100644 index 00000000..1f432adc --- /dev/null +++ b/cut-n-paste/smclient/eggdesktopfile.c @@ -0,0 +1,1477 @@ +/* eggdesktopfile.c - Freedesktop.Org Desktop Files + * Copyright (C) 2007 Novell, Inc. + * + * Based on mate-desktop-item.c + * Copyright (C) 1999, 2000 Red Hat Inc. + * Copyright (C) 2001 George Lebl + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "eggdesktopfile.h" + +#include +#include + +#include +#include +#include + +struct EggDesktopFile { + GKeyFile *key_file; + char *source; + + char *name, *icon; + EggDesktopFileType type; + char document_code; +}; + +/** + * egg_desktop_file_new: + * @desktop_file_path: path to a Freedesktop-style Desktop file + * @error: error pointer + * + * Creates a new #EggDesktopFile for @desktop_file. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new (const char *desktop_file_path, GError **error) +{ + GKeyFile *key_file; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_file (key_file, desktop_file_path, 0, error)) + { + g_key_file_free (key_file); + return NULL; + } + + return egg_desktop_file_new_from_key_file (key_file, desktop_file_path, + error); +} + +/** + * egg_desktop_file_new_from_data_dirs: + * @desktop_file_path: relative path to a Freedesktop-style Desktop file + * @error: error pointer + * + * Looks for @desktop_file_path in the paths returned from + * g_get_user_data_dir() and g_get_system_data_dirs(), and creates + * a new #EggDesktopFile from it. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new_from_data_dirs (const char *desktop_file_path, + GError **error) +{ + EggDesktopFile *desktop_file; + GKeyFile *key_file; + char *full_path; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_data_dirs (key_file, desktop_file_path, + &full_path, 0, error)) + { + g_key_file_free (key_file); + return NULL; + } + + desktop_file = egg_desktop_file_new_from_key_file (key_file, + full_path, + error); + g_free (full_path); + return desktop_file; +} + +/** + * egg_desktop_file_new_from_dirs: + * @desktop_file_path: relative path to a Freedesktop-style Desktop file + * @search_dirs: NULL-terminated array of directories to search + * @error: error pointer + * + * Looks for @desktop_file_path in the paths returned from + * g_get_user_data_dir() and g_get_system_data_dirs(), and creates + * a new #EggDesktopFile from it. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new_from_dirs (const char *desktop_file_path, + const char **search_dirs, + GError **error) +{ + EggDesktopFile *desktop_file; + GKeyFile *key_file; + char *full_path; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_dirs (key_file, desktop_file_path, search_dirs, + &full_path, 0, error)) + { + g_key_file_free (key_file); + return NULL; + } + + desktop_file = egg_desktop_file_new_from_key_file (key_file, + full_path, + error); + g_free (full_path); + return desktop_file; +} + +/** + * egg_desktop_file_new_from_key_file: + * @key_file: a #GKeyFile representing a desktop file + * @source: the path or URI that @key_file was loaded from, or %NULL + * @error: error pointer + * + * Creates a new #EggDesktopFile for @key_file. Assumes ownership of + * @key_file (on success or failure); you should consider @key_file to + * be freed after calling this function. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new_from_key_file (GKeyFile *key_file, + const char *source, + GError **error) +{ + EggDesktopFile *desktop_file; + char *version, *type; + + if (!g_key_file_has_group (key_file, EGG_DESKTOP_FILE_GROUP)) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_INVALID, + _("File is not a valid .desktop file")); + g_key_file_free (key_file); + return NULL; + } + + version = g_key_file_get_value (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_VERSION, + NULL); + if (version) + { + double version_num; + char *end; + + version_num = g_ascii_strtod (version, &end); + if (*end) + { + g_warning ("Invalid Version string '%s' in %s", + version, source ? source : "(unknown)"); + } + else if (version_num > 1.0) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_INVALID, + _("Unrecognized desktop file Version '%s'"), version); + g_free (version); + g_key_file_free (key_file); + return NULL; + } + g_free (version); + } + + desktop_file = g_new0 (EggDesktopFile, 1); + desktop_file->key_file = key_file; + + if (g_path_is_absolute (source)) + desktop_file->source = g_filename_to_uri (source, NULL, NULL); + else + desktop_file->source = g_strdup (source); + + desktop_file->name = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_NAME, error); + if (!desktop_file->name) + { + egg_desktop_file_free (desktop_file); + return NULL; + } + + type = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TYPE, error); + if (!type) + { + egg_desktop_file_free (desktop_file); + return NULL; + } + + if (!strcmp (type, "Application")) + { + char *exec, *p; + + desktop_file->type = EGG_DESKTOP_FILE_TYPE_APPLICATION; + + exec = g_key_file_get_string (key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + error); + if (!exec) + { + egg_desktop_file_free (desktop_file); + g_free (type); + return NULL; + } + + /* See if it takes paths or URIs or neither */ + for (p = exec; *p; p++) + { + if (*p == '%') + { + if (p[1] == '\0' || strchr ("FfUu", p[1])) + { + desktop_file->document_code = p[1]; + break; + } + p++; + } + } + + g_free (exec); + } + else if (!strcmp (type, "Link")) + { + char *url; + + desktop_file->type = EGG_DESKTOP_FILE_TYPE_LINK; + + url = g_key_file_get_string (key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_URL, + error); + if (!url) + { + egg_desktop_file_free (desktop_file); + g_free (type); + return NULL; + } + g_free (url); + } + else if (!strcmp (type, "Directory")) + desktop_file->type = EGG_DESKTOP_FILE_TYPE_DIRECTORY; + else + desktop_file->type = EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED; + + g_free (type); + + /* Check the Icon key */ + desktop_file->icon = g_key_file_get_string (key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_ICON, + NULL); + if (desktop_file->icon && !g_path_is_absolute (desktop_file->icon)) + { + char *ext; + + /* Lots of .desktop files still get this wrong */ + ext = strrchr (desktop_file->icon, '.'); + if (ext && (!strcmp (ext, ".png") || + !strcmp (ext, ".xpm") || + !strcmp (ext, ".svg"))) + { + g_warning ("Desktop file '%s' has malformed Icon key '%s'" + "(should not include extension)", + source ? source : "(unknown)", + desktop_file->icon); + *ext = '\0'; + } + } + + return desktop_file; +} + +/** + * egg_desktop_file_free: + * @desktop_file: an #EggDesktopFile + * + * Frees @desktop_file. + **/ +void +egg_desktop_file_free (EggDesktopFile *desktop_file) +{ + g_key_file_free (desktop_file->key_file); + g_free (desktop_file->source); + g_free (desktop_file->name); + g_free (desktop_file->icon); + g_free (desktop_file); +} + +/** + * egg_desktop_file_get_source: + * @desktop_file: an #EggDesktopFile + * + * Gets the URI that @desktop_file was loaded from. + * + * Return value: @desktop_file's source URI + **/ +const char * +egg_desktop_file_get_source (EggDesktopFile *desktop_file) +{ + return desktop_file->source; +} + +/** + * egg_desktop_file_get_desktop_file_type: + * @desktop_file: an #EggDesktopFile + * + * Gets the desktop file type of @desktop_file. + * + * Return value: @desktop_file's type + **/ +EggDesktopFileType +egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file) +{ + return desktop_file->type; +} + +/** + * egg_desktop_file_get_name: + * @desktop_file: an #EggDesktopFile + * + * Gets the (localized) value of @desktop_file's "Name" key. + * + * Return value: the application/link name + **/ +const char * +egg_desktop_file_get_name (EggDesktopFile *desktop_file) +{ + return desktop_file->name; +} + +/** + * egg_desktop_file_get_icon: + * @desktop_file: an #EggDesktopFile + * + * Gets the value of @desktop_file's "Icon" key. + * + * If the icon string is a full path (that is, if g_path_is_absolute() + * returns %TRUE when called on it), it points to a file containing an + * unthemed icon. If the icon string is not a full path, it is the + * name of a themed icon, which can be looked up with %GtkIconTheme, + * or passed directly to a theme-aware widget like %GtkImage or + * %GtkCellRendererPixbuf. + * + * Return value: the icon path or name + **/ +const char * +egg_desktop_file_get_icon (EggDesktopFile *desktop_file) +{ + return desktop_file->icon; +} + +gboolean +egg_desktop_file_has_key (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_has_key (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char * +egg_desktop_file_get_string (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char * +egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + GError **error) +{ + return g_key_file_get_locale_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, locale, + error); +} + +gboolean +egg_desktop_file_get_boolean (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_boolean (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +double +egg_desktop_file_get_numeric (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_double (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char ** +egg_desktop_file_get_string_list (EggDesktopFile *desktop_file, + const char *key, + gsize *length, + GError **error) +{ + return g_key_file_get_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, length, + error); +} + +char ** +egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + gsize *length, + GError **error) +{ + return g_key_file_get_locale_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + locale, length, + error); +} + +/** + * egg_desktop_file_can_launch: + * @desktop_file: an #EggDesktopFile + * @desktop_environment: the name of the running desktop environment, + * or %NULL + * + * Tests if @desktop_file can/should be launched in the current + * environment. If @desktop_environment is non-%NULL, @desktop_file's + * "OnlyShowIn" and "NotShowIn" keys are checked to make sure that + * this desktop_file is appropriate for the named environment. + * + * Furthermore, if @desktop_file has type + * %EGG_DESKTOP_FILE_TYPE_APPLICATION, its "TryExec" key (if any) is + * also checked, to make sure the binary it points to exists. + * + * egg_desktop_file_can_launch() does NOT check the value of the + * "Hidden" key. + * + * Return value: %TRUE if @desktop_file can be launched + **/ +gboolean +egg_desktop_file_can_launch (EggDesktopFile *desktop_file, + const char *desktop_environment) +{ + char *try_exec, *found_program; + char **only_show_in, **not_show_in; + gboolean found; + int i; + + if (desktop_file->type != EGG_DESKTOP_FILE_TYPE_APPLICATION && + desktop_file->type != EGG_DESKTOP_FILE_TYPE_LINK) + return FALSE; + + if (desktop_environment) + { + only_show_in = g_key_file_get_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN, + NULL, NULL); + if (only_show_in) + { + for (i = 0, found = FALSE; only_show_in[i] && !found; i++) + { + if (!strcmp (only_show_in[i], desktop_environment)) + found = TRUE; + } + + g_strfreev (only_show_in); + + if (!found) + return FALSE; + } + + not_show_in = g_key_file_get_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN, + NULL, NULL); + if (not_show_in) + { + for (i = 0, found = FALSE; not_show_in[i] && !found; i++) + { + if (!strcmp (not_show_in[i], desktop_environment)) + found = TRUE; + } + + g_strfreev (not_show_in); + + if (found) + return FALSE; + } + } + + if (desktop_file->type == EGG_DESKTOP_FILE_TYPE_APPLICATION) + { + try_exec = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TRY_EXEC, + NULL); + if (try_exec) + { + found_program = g_find_program_in_path (try_exec); + g_free (try_exec); + + if (!found_program) + return FALSE; + g_free (found_program); + } + } + + return TRUE; +} + +/** + * egg_desktop_file_accepts_documents: + * @desktop_file: an #EggDesktopFile + * + * Tests if @desktop_file represents an application that can accept + * documents on the command line. + * + * Return value: %TRUE or %FALSE + **/ +gboolean +egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file) +{ + return desktop_file->document_code != 0; +} + +/** + * egg_desktop_file_accepts_multiple: + * @desktop_file: an #EggDesktopFile + * + * Tests if @desktop_file can accept multiple documents at once. + * + * If this returns %FALSE, you can still pass multiple documents to + * egg_desktop_file_launch(), but that will result in multiple copies + * of the application being launched. See egg_desktop_file_launch() + * for more details. + * + * Return value: %TRUE or %FALSE + **/ +gboolean +egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file) +{ + return (desktop_file->document_code == 'F' || + desktop_file->document_code == 'U'); +} + +/** + * egg_desktop_file_accepts_uris: + * @desktop_file: an #EggDesktopFile + * + * Tests if @desktop_file can accept (non-"file:") URIs as documents to + * open. + * + * Return value: %TRUE or %FALSE + **/ +gboolean +egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file) +{ + return (desktop_file->document_code == 'U' || + desktop_file->document_code == 'u'); +} + +static void +append_quoted_word (GString *str, + const char *s, + gboolean in_single_quotes, + gboolean in_double_quotes) +{ + const char *p; + + if (!in_single_quotes && !in_double_quotes) + g_string_append_c (str, '\''); + else if (!in_single_quotes && in_double_quotes) + g_string_append (str, "\"'"); + + if (!strchr (s, '\'')) + g_string_append (str, s); + else + { + for (p = s; *p != '\0'; p++) + { + if (*p == '\'') + g_string_append (str, "'\\''"); + else + g_string_append_c (str, *p); + } + } + + if (!in_single_quotes && !in_double_quotes) + g_string_append_c (str, '\''); + else if (!in_single_quotes && in_double_quotes) + g_string_append (str, "'\""); +} + +static void +do_percent_subst (EggDesktopFile *desktop_file, + char code, + GString *str, + GSList **documents, + gboolean in_single_quotes, + gboolean in_double_quotes) +{ + GSList *d; + char *doc; + + switch (code) + { + case '%': + g_string_append_c (str, '%'); + break; + + case 'F': + case 'U': + for (d = *documents; d; d = d->next) + { + doc = d->data; + g_string_append (str, " "); + append_quoted_word (str, doc, in_single_quotes, in_double_quotes); + } + *documents = NULL; + break; + + case 'f': + case 'u': + if (*documents) + { + doc = (*documents)->data; + g_string_append (str, " "); + append_quoted_word (str, doc, in_single_quotes, in_double_quotes); + *documents = (*documents)->next; + } + break; + + case 'i': + if (desktop_file->icon) + { + g_string_append (str, "--icon "); + append_quoted_word (str, desktop_file->icon, + in_single_quotes, in_double_quotes); + } + break; + + case 'c': + if (desktop_file->name) + { + append_quoted_word (str, desktop_file->name, + in_single_quotes, in_double_quotes); + } + break; + + case 'k': + if (desktop_file->source) + { + append_quoted_word (str, desktop_file->source, + in_single_quotes, in_double_quotes); + } + break; + + case 'D': + case 'N': + case 'd': + case 'n': + case 'v': + case 'm': + /* Deprecated; skip */ + break; + + default: + g_warning ("Unrecognized %%-code '%%%c' in Exec", code); + break; + } +} + +static char * +parse_exec (EggDesktopFile *desktop_file, + GSList **documents, + GError **error) +{ + char *exec, *p, *command; + gboolean escape, single_quot, double_quot; + GString *gs; + + exec = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + error); + if (!exec) + return NULL; + + /* Build the command */ + gs = g_string_new (NULL); + escape = single_quot = double_quot = FALSE; + + for (p = exec; *p != '\0'; p++) + { + if (escape) + { + escape = FALSE; + g_string_append_c (gs, *p); + } + else if (*p == '\\') + { + if (!single_quot) + escape = TRUE; + g_string_append_c (gs, *p); + } + else if (*p == '\'') + { + g_string_append_c (gs, *p); + if (!single_quot && !double_quot) + single_quot = TRUE; + else if (single_quot) + single_quot = FALSE; + } + else if (*p == '"') + { + g_string_append_c (gs, *p); + if (!single_quot && !double_quot) + double_quot = TRUE; + else if (double_quot) + double_quot = FALSE; + } + else if (*p == '%' && p[1]) + { + do_percent_subst (desktop_file, p[1], gs, documents, + single_quot, double_quot); + p++; + } + else + g_string_append_c (gs, *p); + } + + g_free (exec); + command = g_string_free (gs, FALSE); + + /* Prepend "xdg-terminal " if needed (FIXME: use gvfs) */ + if (g_key_file_has_key (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TERMINAL, + NULL)) + { + GError *terminal_error = NULL; + gboolean use_terminal = + g_key_file_get_boolean (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TERMINAL, + &terminal_error); + if (terminal_error) + { + g_free (command); + g_propagate_error (error, terminal_error); + return NULL; + } + + if (use_terminal) + { + gs = g_string_new ("xdg-terminal "); + append_quoted_word (gs, command, FALSE, FALSE); + g_free (command); + command = g_string_free (gs, FALSE); + } + } + + return command; +} + +static GSList * +translate_document_list (EggDesktopFile *desktop_file, GSList *documents) +{ + gboolean accepts_uris = egg_desktop_file_accepts_uris (desktop_file); + GSList *ret, *d; + + for (d = documents, ret = NULL; d; d = d->next) + { + const char *document = d->data; + gboolean is_uri = !g_path_is_absolute (document); + char *translated; + + if (accepts_uris) + { + if (is_uri) + translated = g_strdup (document); + else + translated = g_filename_to_uri (document, NULL, NULL); + } + else + { + if (is_uri) + translated = g_filename_from_uri (document, NULL, NULL); + else + translated = g_strdup (document); + } + + if (translated) + ret = g_slist_prepend (ret, translated); + } + + return g_slist_reverse (ret); +} + +static void +free_document_list (GSList *documents) +{ + GSList *d; + + for (d = documents; d; d = d->next) + g_free (d->data); + g_slist_free (documents); +} + +/** + * egg_desktop_file_parse_exec: + * @desktop_file: a #EggDesktopFile + * @documents: a list of document paths or URIs + * @error: error pointer + * + * Parses @desktop_file's Exec key, inserting @documents into it, and + * returns the result. + * + * If @documents contains non-file: URIs and @desktop_file does not + * accept URIs, those URIs will be ignored. Likewise, if @documents + * contains more elements than @desktop_file accepts, the extra + * documents will be ignored. + * + * Return value: the parsed Exec string + **/ +char * +egg_desktop_file_parse_exec (EggDesktopFile *desktop_file, + GSList *documents, + GError **error) +{ + GSList *translated, *docs; + char *command; + + docs = translated = translate_document_list (desktop_file, documents); + command = parse_exec (desktop_file, &docs, error); + free_document_list (translated); + + return command; +} + +static gboolean +parse_link (EggDesktopFile *desktop_file, + EggDesktopFile **app_desktop_file, + GSList **documents, + GError **error) +{ + char *url; + GKeyFile *key_file; + + url = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_URL, + error); + if (!url) + return FALSE; + *documents = g_slist_prepend (NULL, url); + + /* FIXME: use gvfs */ + key_file = g_key_file_new (); + g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_NAME, + "xdg-open"); + g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_TYPE, + "Application"); + g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + "xdg-open %u"); + *app_desktop_file = egg_desktop_file_new_from_key_file (key_file, NULL, NULL); + return TRUE; +} + +#if GTK_CHECK_VERSION (2, 12, 0) +static char * +start_startup_notification (GdkDisplay *display, + EggDesktopFile *desktop_file, + const char *argv0, + int screen, + int workspace, + guint32 launch_time) +{ + static int sequence = 0; + char *startup_id; + char *description, *wmclass; + char *screen_str, *workspace_str; + + if (g_key_file_has_key (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY, + NULL)) + { + if (!g_key_file_get_boolean (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY, + NULL)) + return NULL; + wmclass = NULL; + } + else + { + wmclass = g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS, + NULL); + if (!wmclass) + return NULL; + } + + if (launch_time == (guint32)-1) + launch_time = gdk_x11_display_get_user_time (display); + startup_id = g_strdup_printf ("%s-%lu-%s-%s-%d_TIME%lu", + g_get_prgname (), + (unsigned long)getpid (), + g_get_host_name (), + argv0, + sequence++, + (unsigned long)launch_time); + + description = g_strdup_printf (_("Starting %s"), desktop_file->name); + screen_str = g_strdup_printf ("%d", screen); + workspace_str = workspace == -1 ? NULL : g_strdup_printf ("%d", workspace); + + gdk_x11_display_broadcast_startup_message (display, "new", + "ID", startup_id, + "NAME", desktop_file->name, + "SCREEN", screen_str, + "BIN", argv0, + "ICON", desktop_file->icon, + "DESKTOP", workspace_str, + "DESCRIPTION", description, + "WMCLASS", wmclass, + NULL); + + g_free (description); + g_free (wmclass); + g_free (screen_str); + g_free (workspace_str); + + return startup_id; +} + +static void +end_startup_notification (GdkDisplay *display, + const char *startup_id) +{ + gdk_x11_display_broadcast_startup_message (display, "remove", + "ID", startup_id, + NULL); +} + +#define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH (30 /* seconds */) + +typedef struct { + GdkDisplay *display; + char *startup_id; +} StartupNotificationData; + +static gboolean +startup_notification_timeout (gpointer data) +{ + StartupNotificationData *sn_data = data; + + end_startup_notification (sn_data->display, sn_data->startup_id); + g_object_unref (sn_data->display); + g_free (sn_data->startup_id); + g_free (sn_data); + + return FALSE; +} + +static void +set_startup_notification_timeout (GdkDisplay *display, + const char *startup_id) +{ + StartupNotificationData *sn_data; + + sn_data = g_new (StartupNotificationData, 1); + sn_data->display = g_object_ref (display); + sn_data->startup_id = g_strdup (startup_id); + + g_timeout_add_seconds (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH, + startup_notification_timeout, sn_data); +} +#endif /* GTK 2.12 */ + +static GPtrArray * +array_putenv (GPtrArray *env, char *variable) +{ + guint i, keylen; + + if (!env) + { + char **envp; + + env = g_ptr_array_new (); + + envp = g_listenv (); + for (i = 0; envp[i]; i++) + { + const char *value; + + value = g_getenv (envp[i]); + g_ptr_array_add (env, g_strdup_printf ("%s=%s", envp[i], + value ? value : "")); + } + g_strfreev (envp); + } + + keylen = strcspn (variable, "="); + + /* Remove old value of key */ + for (i = 0; i < env->len; i++) + { + char *envvar = env->pdata[i]; + + if (!strncmp (envvar, variable, keylen) && envvar[keylen] == '=') + { + g_free (envvar); + g_ptr_array_remove_index_fast (env, i); + break; + } + } + + /* Add new value */ + g_ptr_array_add (env, g_strdup (variable)); + + return env; +} + +static gboolean +egg_desktop_file_launchv (EggDesktopFile *desktop_file, + GSList *documents, va_list args, + GError **error) +{ + EggDesktopFileLaunchOption option; + GSList *translated_documents = NULL, *docs = NULL; + char *command, **argv; + int argc, i, screen_num; + gboolean success, current_success; + GdkDisplay *display; + char *startup_id; + + GPtrArray *env = NULL; + char **variables = NULL; + GdkScreen *screen = NULL; + int workspace = -1; + const char *directory = NULL; + guint32 launch_time = (guint32)-1; + GSpawnFlags flags = G_SPAWN_SEARCH_PATH; + GSpawnChildSetupFunc setup_func = NULL; + gpointer setup_data = NULL; + + GPid *ret_pid = NULL; + int *ret_stdin = NULL, *ret_stdout = NULL, *ret_stderr = NULL; + char **ret_startup_id = NULL; + + if (documents && desktop_file->document_code == 0) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + _("Application does not accept documents on command line")); + return FALSE; + } + + /* Read the options: technically it's incorrect for the caller to + * NULL-terminate the list of options (rather than 0-terminating + * it), but NULL-terminating lets us use G_GNUC_NULL_TERMINATED, + * it's more consistent with other glib/gtk methods, and it will + * work as long as sizeof (int) <= sizeof (NULL), and NULL is + * represented as 0. (Which is true everywhere we care about.) + */ + while ((option = va_arg (args, EggDesktopFileLaunchOption))) + { + switch (option) + { + case EGG_DESKTOP_FILE_LAUNCH_CLEARENV: + if (env) + g_ptr_array_free (env, TRUE); + env = g_ptr_array_new (); + break; + case EGG_DESKTOP_FILE_LAUNCH_PUTENV: + variables = va_arg (args, char **); + for (i = 0; variables[i]; i++) + env = array_putenv (env, variables[i]); + break; + + case EGG_DESKTOP_FILE_LAUNCH_SCREEN: + screen = va_arg (args, GdkScreen *); + break; + case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: + workspace = va_arg (args, int); + break; + + case EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: + directory = va_arg (args, const char *); + break; + case EGG_DESKTOP_FILE_LAUNCH_TIME: + launch_time = va_arg (args, guint32); + break; + case EGG_DESKTOP_FILE_LAUNCH_FLAGS: + flags |= va_arg (args, GSpawnFlags); + /* Make sure they didn't set any flags that don't make sense. */ + flags &= ~G_SPAWN_FILE_AND_ARGV_ZERO; + break; + case EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC: + setup_func = va_arg (args, GSpawnChildSetupFunc); + setup_data = va_arg (args, gpointer); + break; + + case EGG_DESKTOP_FILE_LAUNCH_RETURN_PID: + ret_pid = va_arg (args, GPid *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE: + ret_stdin = va_arg (args, int *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE: + ret_stdout = va_arg (args, int *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE: + ret_stderr = va_arg (args, int *); + break; + case EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID: + ret_startup_id = va_arg (args, char **); + break; + + default: + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION, + _("Unrecognized launch option: %d"), + GPOINTER_TO_INT (option)); + success = FALSE; + goto out; + } + } + + if (screen) + { + char *display_name = gdk_screen_make_display_name (screen); + char *display_env = g_strdup_printf ("DISPLAY=%s", display_name); + env = array_putenv (env, display_env); + g_free (display_name); + g_free (display_env); + + display = gdk_screen_get_display (screen); + } + else + { + display = gdk_display_get_default (); + screen = gdk_display_get_default_screen (display); + } + screen_num = gdk_screen_get_number (screen); + + translated_documents = translate_document_list (desktop_file, documents); + docs = translated_documents; + + success = FALSE; + + do + { + command = parse_exec (desktop_file, &docs, error); + if (!command) + goto out; + + if (!g_shell_parse_argv (command, &argc, &argv, error)) + { + g_free (command); + goto out; + } + g_free (command); + +#if GTK_CHECK_VERSION (2, 12, 0) + startup_id = start_startup_notification (display, desktop_file, + argv[0], screen_num, + workspace, launch_time); + if (startup_id) + { + char *startup_id_env = g_strdup_printf ("DESKTOP_STARTUP_ID=%s", + startup_id); + env = array_putenv (env, startup_id_env); + g_free (startup_id_env); + } +#else + startup_id = NULL; +#endif /* GTK 2.12 */ + + if (env != NULL) + g_ptr_array_add (env, NULL); + + current_success = + g_spawn_async_with_pipes (directory, + argv, + env ? (char **)(env->pdata) : NULL, + flags, + setup_func, setup_data, + ret_pid, + ret_stdin, ret_stdout, ret_stderr, + error); + g_strfreev (argv); + + if (startup_id) + { +#if GTK_CHECK_VERSION (2, 12, 0) + if (current_success) + { + set_startup_notification_timeout (display, startup_id); + + if (ret_startup_id) + *ret_startup_id = startup_id; + else + g_free (startup_id); + } + else +#endif /* GTK 2.12 */ + g_free (startup_id); + } + else if (ret_startup_id) + *ret_startup_id = NULL; + + if (current_success) + { + /* If we successfully launch any instances of the app, make + * sure we return TRUE and don't set @error. + */ + success = TRUE; + error = NULL; + + /* Also, only set the output params on the first one */ + ret_pid = NULL; + ret_stdin = ret_stdout = ret_stderr = NULL; + ret_startup_id = NULL; + } + } + while (docs && current_success); + + out: + if (env) + { + g_strfreev ((char **)env->pdata); + g_ptr_array_free (env, FALSE); + } + free_document_list (translated_documents); + + return success; +} + +/** + * egg_desktop_file_launch: + * @desktop_file: an #EggDesktopFile + * @documents: a list of URIs or paths to documents to open + * @error: error pointer + * @...: additional options + * + * Launches @desktop_file with the given arguments. Additional options + * can be specified as follows: + * + * %EGG_DESKTOP_FILE_LAUNCH_CLEARENV: (no arguments) + * clears the environment in the child process + * %EGG_DESKTOP_FILE_LAUNCH_PUTENV: (char **variables) + * adds the NAME=VALUE strings in the given %NULL-terminated + * array to the child process's environment + * %EGG_DESKTOP_FILE_LAUNCH_SCREEN: (GdkScreen *screen) + * causes the application to be launched on the given screen + * %EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: (int workspace) + * causes the application to be launched on the given workspace + * %EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: (char *dir) + * causes the application to be launched in the given directory + * %EGG_DESKTOP_FILE_LAUNCH_TIME: (guint32 launch_time) + * sets the "launch time" for the application. If the user + * interacts with another window after @launch_time but before + * the launched application creates its first window, the window + * manager may choose to not give focus to the new application. + * Passing 0 for @launch_time will explicitly request that the + * application not receive focus. + * %EGG_DESKTOP_FILE_LAUNCH_FLAGS (GSpawnFlags flags) + * Sets additional #GSpawnFlags to use. See g_spawn_async() for + * more details. + * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC (GSpawnChildSetupFunc, gpointer) + * Sets the child setup callback and the data to pass to it. + * (See g_spawn_async() for more details.) + * + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID (GPid **pid) + * On a successful launch, sets *@pid to the PID of the launched + * application. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID (char **startup_id) + * On a successful launch, sets *@startup_id to the Startup + * Notification "startup id" of the launched application. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE (int *fd) + * On a successful launch, sets *@fd to the file descriptor of + * a pipe connected to the application's stdin. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE (int *fd) + * On a successful launch, sets *@fd to the file descriptor of + * a pipe connected to the application's stdout. + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE (int *fd) + * On a successful launch, sets *@fd to the file descriptor of + * a pipe connected to the application's stderr. + * + * The options should be terminated with a single %NULL. + * + * If @documents contains multiple documents, but + * egg_desktop_file_accepts_multiple() returns %FALSE for + * @desktop_file, then egg_desktop_file_launch() will actually launch + * multiple instances of the application. In that case, the return + * value (as well as any values passed via + * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, etc) will only reflect the + * first instance of the application that was launched (but the + * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC will be called for each + * instance). + * + * Return value: %TRUE if the application was successfully launched. + **/ +gboolean +egg_desktop_file_launch (EggDesktopFile *desktop_file, + GSList *documents, GError **error, + ...) +{ + va_list args; + gboolean success; + EggDesktopFile *app_desktop_file; + + switch (desktop_file->type) + { + case EGG_DESKTOP_FILE_TYPE_APPLICATION: + va_start (args, error); + success = egg_desktop_file_launchv (desktop_file, documents, + args, error); + va_end (args); + break; + + case EGG_DESKTOP_FILE_TYPE_LINK: + if (documents) + { + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + _("Can't pass document URIs to a 'Type=Link' desktop entry")); + return FALSE; + } + + if (!parse_link (desktop_file, &app_desktop_file, &documents, error)) + return FALSE; + + va_start (args, error); + success = egg_desktop_file_launchv (app_desktop_file, documents, + args, error); + va_end (args); + + egg_desktop_file_free (app_desktop_file); + free_document_list (documents); + break; + + default: + g_set_error (error, EGG_DESKTOP_FILE_ERROR, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + _("Not a launchable item")); + success = FALSE; + break; + } + + return success; +} + + +GQuark +egg_desktop_file_error_quark (void) +{ + return g_quark_from_static_string ("egg-desktop_file-error-quark"); +} + + +G_LOCK_DEFINE_STATIC (egg_desktop_file); +static EggDesktopFile *egg_desktop_file; + +/** + * egg_set_desktop_file: + * @desktop_file_path: path to the application's desktop file + * + * Creates an #EggDesktopFile for the application from the data at + * @desktop_file_path. This will also call g_set_application_name() + * with the localized application name from the desktop file, and + * gtk_window_set_default_icon_name() or + * gtk_window_set_default_icon_from_file() with the application's + * icon. Other code may use additional information from the desktop + * file. + * + * Note that for thread safety reasons, this function can only + * be called once. + **/ +void +egg_set_desktop_file (const char *desktop_file_path) +{ + GError *error = NULL; + + G_LOCK (egg_desktop_file); + if (egg_desktop_file) + egg_desktop_file_free (egg_desktop_file); + + egg_desktop_file = egg_desktop_file_new (desktop_file_path, &error); + if (error) + { + g_warning ("Could not load desktop file '%s': %s", + desktop_file_path, error->message); + g_error_free (error); + } + + if (egg_desktop_file) { + /* Set localized application name and default window icon */ + if (egg_desktop_file->name) + g_set_application_name (egg_desktop_file->name); + if (egg_desktop_file->icon) + { + if (g_path_is_absolute (egg_desktop_file->icon)) + gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL); + else + gtk_window_set_default_icon_name (egg_desktop_file->icon); + } + } + + G_UNLOCK (egg_desktop_file); +} + +/** + * egg_get_desktop_file: + * + * Gets the application's #EggDesktopFile, as set by + * egg_set_desktop_file(). + * + * Return value: the #EggDesktopFile, or %NULL if it hasn't been set. + **/ +EggDesktopFile * +egg_get_desktop_file (void) +{ + EggDesktopFile *retval; + + G_LOCK (egg_desktop_file); + retval = egg_desktop_file; + G_UNLOCK (egg_desktop_file); + + return retval; +} diff --git a/cut-n-paste/smclient/eggdesktopfile.h b/cut-n-paste/smclient/eggdesktopfile.h new file mode 100644 index 00000000..2be36210 --- /dev/null +++ b/cut-n-paste/smclient/eggdesktopfile.h @@ -0,0 +1,159 @@ +/* eggdesktopfile.h - Freedesktop.Org Desktop Files + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __EGG_DESKTOP_FILE_H__ +#define __EGG_DESKTOP_FILE_H__ + +#include + +G_BEGIN_DECLS + +typedef struct EggDesktopFile EggDesktopFile; + +typedef enum { + EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED, + + EGG_DESKTOP_FILE_TYPE_APPLICATION, + EGG_DESKTOP_FILE_TYPE_LINK, + EGG_DESKTOP_FILE_TYPE_DIRECTORY +} EggDesktopFileType; + +EggDesktopFile *egg_desktop_file_new (const char *desktop_file_path, + GError **error); + +EggDesktopFile *egg_desktop_file_new_from_data_dirs (const char *desktop_file_path, + GError **error); +EggDesktopFile *egg_desktop_file_new_from_dirs (const char *desktop_file_path, + const char **search_dirs, + GError **error); +EggDesktopFile *egg_desktop_file_new_from_key_file (GKeyFile *key_file, + const char *source, + GError **error); + +void egg_desktop_file_free (EggDesktopFile *desktop_file); + +const char *egg_desktop_file_get_source (EggDesktopFile *desktop_file); + +EggDesktopFileType egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file); + +const char *egg_desktop_file_get_name (EggDesktopFile *desktop_file); +const char *egg_desktop_file_get_icon (EggDesktopFile *desktop_file); + +gboolean egg_desktop_file_can_launch (EggDesktopFile *desktop_file, + const char *desktop_environment); + +gboolean egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file); +gboolean egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file); +gboolean egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file); + +char *egg_desktop_file_parse_exec (EggDesktopFile *desktop_file, + GSList *documents, + GError **error); + +gboolean egg_desktop_file_launch (EggDesktopFile *desktop_file, + GSList *documents, + GError **error, + ...) G_GNUC_NULL_TERMINATED; + +typedef enum { + EGG_DESKTOP_FILE_LAUNCH_CLEARENV = 1, + EGG_DESKTOP_FILE_LAUNCH_PUTENV, + EGG_DESKTOP_FILE_LAUNCH_SCREEN, + EGG_DESKTOP_FILE_LAUNCH_WORKSPACE, + EGG_DESKTOP_FILE_LAUNCH_DIRECTORY, + EGG_DESKTOP_FILE_LAUNCH_TIME, + EGG_DESKTOP_FILE_LAUNCH_FLAGS, + EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC, + EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE, + EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID +} EggDesktopFileLaunchOption; + +/* Standard Keys */ +#define EGG_DESKTOP_FILE_GROUP "Desktop Entry" + +#define EGG_DESKTOP_FILE_KEY_TYPE "Type" +#define EGG_DESKTOP_FILE_KEY_VERSION "Version" +#define EGG_DESKTOP_FILE_KEY_NAME "Name" +#define EGG_DESKTOP_FILE_KEY_GENERIC_NAME "GenericName" +#define EGG_DESKTOP_FILE_KEY_NO_DISPLAY "NoDisplay" +#define EGG_DESKTOP_FILE_KEY_COMMENT "Comment" +#define EGG_DESKTOP_FILE_KEY_ICON "Icon" +#define EGG_DESKTOP_FILE_KEY_HIDDEN "Hidden" +#define EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN "OnlyShowIn" +#define EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN "NotShowIn" +#define EGG_DESKTOP_FILE_KEY_TRY_EXEC "TryExec" +#define EGG_DESKTOP_FILE_KEY_EXEC "Exec" +#define EGG_DESKTOP_FILE_KEY_PATH "Path" +#define EGG_DESKTOP_FILE_KEY_TERMINAL "Terminal" +#define EGG_DESKTOP_FILE_KEY_MIME_TYPE "MimeType" +#define EGG_DESKTOP_FILE_KEY_CATEGORIES "Categories" +#define EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY "StartupNotify" +#define EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS "StartupWMClass" +#define EGG_DESKTOP_FILE_KEY_URL "URL" + +/* Accessors */ +gboolean egg_desktop_file_has_key (EggDesktopFile *desktop_file, + const char *key, + GError **error); +char *egg_desktop_file_get_string (EggDesktopFile *desktop_file, + const char *key, + GError **error) G_GNUC_MALLOC; +char *egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + GError **error) G_GNUC_MALLOC; +gboolean egg_desktop_file_get_boolean (EggDesktopFile *desktop_file, + const char *key, + GError **error); +double egg_desktop_file_get_numeric (EggDesktopFile *desktop_file, + const char *key, + GError **error); +char **egg_desktop_file_get_string_list (EggDesktopFile *desktop_file, + const char *key, + gsize *length, + GError **error) G_GNUC_MALLOC; +char **egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + gsize *length, + GError **error) G_GNUC_MALLOC; + + +/* Errors */ +#define EGG_DESKTOP_FILE_ERROR egg_desktop_file_error_quark() + +GQuark egg_desktop_file_error_quark (void); + +typedef enum { + EGG_DESKTOP_FILE_ERROR_INVALID, + EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, + EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION +} EggDesktopFileError; + +/* Global application desktop file */ +void egg_set_desktop_file (const char *desktop_file_path); +EggDesktopFile *egg_get_desktop_file (void); + + +G_END_DECLS + +#endif /* __EGG_DESKTOP_FILE_H__ */ diff --git a/cut-n-paste/smclient/eggsmclient-osx.c b/cut-n-paste/smclient/eggsmclient-osx.c new file mode 100644 index 00000000..5428c09f --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient-osx.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* EggSMClientOSX + * + * For details on the OS X logout process, see: + * http://developer.apple.com/documentation/MacOSX/Conceptual/BPSystemStartup/Articles/BootProcess.html#//apple_ref/doc/uid/20002130-114618 + * + * EggSMClientOSX registers for the kAEQuitApplication AppleEvent; the + * handler we register (quit_requested()) will be invoked from inside + * the quartz event-handling code (specifically, from inside + * [NSApplication nextEventMatchingMask]) when an AppleEvent arrives. + * We use AESuspendTheCurrentEvent() and AEResumeTheCurrentEvent() to + * allow asynchronous / non-main-loop-reentering processing of the + * quit request. (These are part of the Carbon framework; it doesn't + * seem to be possible to handle AppleEvents asynchronously from + * Cocoa.) + */ + +#include "config.h" + +#include "eggsmclient-private.h" +#include +#include +#include + +#define EGG_TYPE_SM_CLIENT_OSX (egg_sm_client_osx_get_type ()) +#define EGG_SM_CLIENT_OSX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSX)) +#define EGG_SM_CLIENT_OSX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSXClass)) +#define EGG_IS_SM_CLIENT_OSX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_OSX)) +#define EGG_IS_SM_CLIENT_OSX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_OSX)) +#define EGG_SM_CLIENT_OSX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSXClass)) + +typedef struct _EggSMClientOSX EggSMClientOSX; +typedef struct _EggSMClientOSXClass EggSMClientOSXClass; + +struct _EggSMClientOSX { + EggSMClient parent; + + AppleEvent quit_event, quit_reply; + gboolean quit_requested, quitting; +}; + +struct _EggSMClientOSXClass +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_osx_startup (EggSMClient *client, + const char *client_id); +static void sm_client_osx_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_osx_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static pascal OSErr quit_requested (const AppleEvent *, AppleEvent *, long); + +G_DEFINE_TYPE (EggSMClientOSX, egg_sm_client_osx, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_osx_init (EggSMClientOSX *osx) +{ + ; +} + +static void +egg_sm_client_osx_class_init (EggSMClientOSXClass *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_osx_startup; + sm_client_class->will_quit = sm_client_osx_will_quit; + sm_client_class->end_session = sm_client_osx_end_session; +} + +EggSMClient * +egg_sm_client_osx_new (void) +{ + return g_object_new (EGG_TYPE_SM_CLIENT_OSX, NULL); +} + +static void +sm_client_osx_startup (EggSMClient *client, + const char *client_id) +{ + AEInstallEventHandler (kCoreEventClass, kAEQuitApplication, + NewAEEventHandlerUPP (quit_requested), + (long)GPOINTER_TO_SIZE (client), false); +} + +static gboolean +idle_quit_requested (gpointer client) +{ + egg_sm_client_quit_requested (client); + return FALSE; +} + +static pascal OSErr +quit_requested (const AppleEvent *aevt, AppleEvent *reply, long refcon) +{ + EggSMClient *client = GSIZE_TO_POINTER ((gsize)refcon); + EggSMClientOSX *osx = GSIZE_TO_POINTER ((gsize)refcon); + + g_return_val_if_fail (!osx->quit_requested, userCanceledErr); + + /* FIXME AEInteractWithUser? */ + + osx->quit_requested = TRUE; + AEDuplicateDesc (aevt, &osx->quit_event); + AEDuplicateDesc (reply, &osx->quit_reply); + AESuspendTheCurrentEvent (aevt); + + /* Don't emit the "quit_requested" signal immediately, since we're + * called from a weird point in the guts of gdkeventloop-quartz.c + */ + g_idle_add (idle_quit_requested, client); + return noErr; +} + +static pascal OSErr +quit_requested_resumed (const AppleEvent *aevt, AppleEvent *reply, long refcon) +{ + EggSMClientOSX *osx = GSIZE_TO_POINTER ((gsize)refcon); + + osx->quit_requested = FALSE; + return osx->quitting ? noErr : userCanceledErr; +} + +static gboolean +idle_will_quit (gpointer client) +{ + EggSMClientOSX *osx = (EggSMClientOSX *)client; + + /* Resume the event with a new handler that will return a value to + * the system. + */ + AEResumeTheCurrentEvent (&osx->quit_event, &osx->quit_reply, + NewAEEventHandlerUPP (quit_requested_resumed), + (long)GPOINTER_TO_SIZE (client)); + AEDisposeDesc (&osx->quit_event); + AEDisposeDesc (&osx->quit_reply); + + if (osx->quitting) + egg_sm_client_quit (client); + return FALSE; +} + +static void +sm_client_osx_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientOSX *osx = (EggSMClientOSX *)client; + + g_return_if_fail (osx->quit_requested); + + osx->quitting = will_quit; + + /* Finish in an idle handler since the caller might have called + * egg_sm_client_will_quit() from inside the "quit_requested" signal + * handler, but may not expect the "quit" signal to arrive during + * the _will_quit() call. + */ + g_idle_add (idle_will_quit, client); +} + +static gboolean +sm_client_osx_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation) +{ + static const ProcessSerialNumber loginwindow_psn = { 0, kSystemProcess }; + AppleEvent event = { typeNull, NULL }, reply = { typeNull, NULL }; + AEAddressDesc target; + AEEventID id; + OSErr err; + + switch (style) + { + case EGG_SM_CLIENT_END_SESSION_DEFAULT: + case EGG_SM_CLIENT_LOGOUT: + id = request_confirmation ? kAELogOut : kAEReallyLogOut; + break; + case EGG_SM_CLIENT_REBOOT: + id = request_confirmation ? kAEShowRestartDialog : kAERestart; + break; + case EGG_SM_CLIENT_SHUTDOWN: + id = request_confirmation ? kAEShowShutdownDialog : kAEShutDown; + break; + } + + err = AECreateDesc (typeProcessSerialNumber, &loginwindow_psn, + sizeof (loginwindow_psn), &target); + if (err != noErr) + { + g_warning ("Could not create descriptor for loginwindow: %d", err); + return FALSE; + } + + err = AECreateAppleEvent (kCoreEventClass, id, &target, + kAutoGenerateReturnID, kAnyTransactionID, + &event); + AEDisposeDesc (&target); + if (err != noErr) + { + g_warning ("Could not create logout AppleEvent: %d", err); + return FALSE; + } + + err = AESend (&event, &reply, kAENoReply, kAENormalPriority, + kAEDefaultTimeout, NULL, NULL); + AEDisposeDesc (&event); + if (err == noErr) + AEDisposeDesc (&reply); + + return err == noErr; +} diff --git a/cut-n-paste/smclient/eggsmclient-private.h b/cut-n-paste/smclient/eggsmclient-private.h new file mode 100644 index 00000000..fc3a0a2d --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient-private.h @@ -0,0 +1,53 @@ +/* eggsmclient-private.h + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __EGG_SM_CLIENT_PRIVATE_H__ +#define __EGG_SM_CLIENT_PRIVATE_H__ + +#include +#include "eggsmclient.h" + +G_BEGIN_DECLS + +GKeyFile *egg_sm_client_save_state (EggSMClient *client); +void egg_sm_client_quit_requested (EggSMClient *client); +void egg_sm_client_quit_cancelled (EggSMClient *client); +void egg_sm_client_quit (EggSMClient *client); + +#if defined (GDK_WINDOWING_X11) +# ifdef EGG_SM_CLIENT_BACKEND_XSMP +GType egg_sm_client_xsmp_get_type (void); +EggSMClient *egg_sm_client_xsmp_new (void); +# endif +# ifdef EGG_SM_CLIENT_BACKEND_DBUS +GType egg_sm_client_dbus_get_type (void); +EggSMClient *egg_sm_client_dbus_new (void); +# endif +#elif defined (GDK_WINDOWING_WIN32) +GType egg_sm_client_win32_get_type (void); +EggSMClient *egg_sm_client_win32_new (void); +#elif defined (GDK_WINDOWING_QUARTZ) +GType egg_sm_client_osx_get_type (void); +EggSMClient *egg_sm_client_osx_new (void); +#endif + +G_END_DECLS + + +#endif /* __EGG_SM_CLIENT_PRIVATE_H__ */ diff --git a/cut-n-paste/smclient/eggsmclient-win32.c b/cut-n-paste/smclient/eggsmclient-win32.c new file mode 100644 index 00000000..3fec775c --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient-win32.c @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* EggSMClientWin32 + * + * For details on the Windows XP logout process, see: + * http://msdn.microsoft.com/en-us/library/aa376876.aspx. + * + * Vista adds some new APIs which EggSMClient does not make use of; see + * http://msdn.microsoft.com/en-us/library/ms700677(VS.85).aspx + * + * When shutting down, Windows sends every top-level window a + * WM_QUERYENDSESSION event, which the application must respond to + * synchronously, saying whether or not it will quit. To avoid main + * loop re-entrancy problems (and to avoid having to muck about too + * much with the guts of the gdk-win32 main loop), we watch for this + * event in a separate thread, which then signals the main thread and + * waits for the main thread to handle the event. Since we don't want + * to require g_thread_init() to be called, we do this all using + * Windows-specific thread methods. + * + * After the application handles the WM_QUERYENDSESSION event, + * Windows then sends it a WM_ENDSESSION event with a TRUE or FALSE + * parameter indicating whether the session is or is not actually + * going to end now. We handle this from the other thread as well. + * + * As mentioned above, Vista introduces several additional new APIs + * that don't fit into the (current) EggSMClient API. Windows also has + * an entirely separate shutdown-notification scheme for non-GUI apps, + * which we also don't handle here. + */ + +#include "config.h" + +#include "eggsmclient-private.h" +#include + +#define WIN32_LEAN_AND_MEAN +#define UNICODE +#include +#include + +#define EGG_TYPE_SM_CLIENT_WIN32 (egg_sm_client_win32_get_type ()) +#define EGG_SM_CLIENT_WIN32(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32)) +#define EGG_SM_CLIENT_WIN32_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class)) +#define EGG_IS_SM_CLIENT_WIN32(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_WIN32)) +#define EGG_IS_SM_CLIENT_WIN32_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_WIN32)) +#define EGG_SM_CLIENT_WIN32_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32Class)) + +typedef struct _EggSMClientWin32 EggSMClientWin32; +typedef struct _EggSMClientWin32Class EggSMClientWin32Class; + +struct _EggSMClientWin32 { + EggSMClient parent; + + HANDLE message_event, response_event; + + volatile GSourceFunc event; + volatile gboolean will_quit; +}; + +struct _EggSMClientWin32Class +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_win32_startup (EggSMClient *client, + const char *client_id); +static void sm_client_win32_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_win32_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static GSource *g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, + gpointer user_data); +static gboolean got_message (gpointer user_data); +static void sm_client_thread (gpointer data); + +G_DEFINE_TYPE (EggSMClientWin32, egg_sm_client_win32, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_win32_init (EggSMClientWin32 *win32) +{ + ; +} + +static void +egg_sm_client_win32_class_init (EggSMClientWin32Class *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_win32_startup; + sm_client_class->will_quit = sm_client_win32_will_quit; + sm_client_class->end_session = sm_client_win32_end_session; +} + +EggSMClient * +egg_sm_client_win32_new (void) +{ + return g_object_new (EGG_TYPE_SM_CLIENT_WIN32, NULL); +} + +static void +sm_client_win32_startup (EggSMClient *client, + const char *client_id) +{ + EggSMClientWin32 *win32 = (EggSMClientWin32 *)client; + + win32->message_event = CreateEvent (NULL, FALSE, FALSE, NULL); + win32->response_event = CreateEvent (NULL, FALSE, FALSE, NULL); + g_win32_handle_source_add (win32->message_event, got_message, win32); + _beginthread (sm_client_thread, 0, client); +} + +static void +sm_client_win32_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientWin32 *win32 = (EggSMClientWin32 *)client; + + win32->will_quit = will_quit; + SetEvent (win32->response_event); +} + +static gboolean +sm_client_win32_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation) +{ + UINT uFlags = EWX_LOGOFF; + + switch (style) + { + case EGG_SM_CLIENT_END_SESSION_DEFAULT: + case EGG_SM_CLIENT_LOGOUT: + uFlags = EWX_LOGOFF; + break; + case EGG_SM_CLIENT_REBOOT: + uFlags = EWX_REBOOT; + break; + case EGG_SM_CLIENT_SHUTDOWN: + uFlags = EWX_POWEROFF; + break; + } + + /* There's no way to make ExitWindowsEx() show a logout dialog, so + * we ignore @request_confirmation. + */ + +#ifdef SHTDN_REASON_FLAG_PLANNED + ExitWindowsEx (uFlags, SHTDN_REASON_FLAG_PLANNED); +#else + ExitWindowsEx (uFlags, 0); +#endif + + return TRUE; +} + + +/* callbacks from logout-listener thread */ + +static gboolean +emit_quit_requested (gpointer smclient) +{ + gdk_threads_enter (); + egg_sm_client_quit_requested (smclient); + gdk_threads_leave (); + + return FALSE; +} + +static gboolean +emit_quit (gpointer smclient) +{ + EggSMClientWin32 *win32 = smclient; + + gdk_threads_enter (); + egg_sm_client_quit (smclient); + gdk_threads_leave (); + + SetEvent (win32->response_event); + return FALSE; +} + +static gboolean +emit_quit_cancelled (gpointer smclient) +{ + EggSMClientWin32 *win32 = smclient; + + gdk_threads_enter (); + egg_sm_client_quit_cancelled (smclient); + gdk_threads_leave (); + + SetEvent (win32->response_event); + return FALSE; +} + +static gboolean +got_message (gpointer smclient) +{ + EggSMClientWin32 *win32 = smclient; + + win32->event (win32); + return TRUE; +} + +/* Windows HANDLE GSource */ + +typedef struct { + GSource source; + GPollFD pollfd; +} GWin32HandleSource; + +static gboolean +g_win32_handle_source_prepare (GSource *source, gint *timeout) +{ + *timeout = -1; + return FALSE; +} + +static gboolean +g_win32_handle_source_check (GSource *source) +{ + GWin32HandleSource *hsource = (GWin32HandleSource *)source; + + return hsource->pollfd.revents; +} + +static gboolean +g_win32_handle_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) +{ + return (*callback) (user_data); +} + +static void +g_win32_handle_source_finalize (GSource *source) +{ + ; +} + +GSourceFuncs g_win32_handle_source_funcs = { + g_win32_handle_source_prepare, + g_win32_handle_source_check, + g_win32_handle_source_dispatch, + g_win32_handle_source_finalize +}; + +static GSource * +g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, gpointer user_data) +{ + GWin32HandleSource *hsource; + GSource *source; + + source = g_source_new (&g_win32_handle_source_funcs, sizeof (GWin32HandleSource)); + hsource = (GWin32HandleSource *)source; + hsource->pollfd.fd = (int)handle; + hsource->pollfd.events = G_IO_IN; + hsource->pollfd.revents = 0; + g_source_add_poll (source, &hsource->pollfd); + + g_source_set_callback (source, callback, user_data, NULL); + g_source_attach (source, NULL); + return source; +} + +/* logout-listener thread */ + +LRESULT CALLBACK +sm_client_win32_window_procedure (HWND hwnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + EggSMClientWin32 *win32 = + (EggSMClientWin32 *)GetWindowLongPtr (hwnd, GWLP_USERDATA); + + switch (message) + { + case WM_QUERYENDSESSION: + win32->event = emit_quit_requested; + SetEvent (win32->message_event); + + WaitForSingleObject (win32->response_event, INFINITE); + return win32->will_quit; + + case WM_ENDSESSION: + if (wParam) + { + /* The session is ending */ + win32->event = emit_quit; + } + else + { + /* Nope, the session *isn't* ending */ + win32->event = emit_quit_cancelled; + } + + SetEvent (win32->message_event); + WaitForSingleObject (win32->response_event, INFINITE); + + return 0; + + default: + return DefWindowProc (hwnd, message, wParam, lParam); + } +} + +static void +sm_client_thread (gpointer smclient) +{ + HINSTANCE instance; + WNDCLASSEXW wcl; + ATOM klass; + HWND window; + MSG msg; + + instance = GetModuleHandle (NULL); + + memset (&wcl, 0, sizeof (WNDCLASSEX)); + wcl.cbSize = sizeof (WNDCLASSEX); + wcl.lpfnWndProc = sm_client_win32_window_procedure; + wcl.hInstance = instance; + wcl.lpszClassName = L"EggSmClientWindow"; + klass = RegisterClassEx (&wcl); + + window = CreateWindowEx (0, MAKEINTRESOURCE (klass), + L"EggSmClientWindow", 0, + 10, 10, 50, 50, GetDesktopWindow (), + NULL, instance, NULL); + SetWindowLongPtr (window, GWLP_USERDATA, (LONG_PTR)smclient); + + /* main loop */ + while (GetMessage (&msg, NULL, 0, 0)) + DispatchMessage (&msg); +} diff --git a/cut-n-paste/smclient/eggsmclient-xsmp.c b/cut-n-paste/smclient/eggsmclient-xsmp.c new file mode 100644 index 00000000..90f201d9 --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient-xsmp.c @@ -0,0 +1,1370 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * + * Inspired by various other pieces of code including GsmClient (C) + * 2001 Havoc Pennington, MateClient (C) 1998 Carsten Schaar, and twm + * session code (C) 1998 The Open Group. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include "eggsmclient.h" +#include "eggsmclient-private.h" + +#include "eggdesktopfile.h" + +#include +#include +#include +#include +#include +#include + +#include + +#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ()) +#define EGG_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMP)) +#define EGG_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass)) +#define EGG_IS_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_XSMP)) +#define EGG_IS_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_XSMP)) +#define EGG_SM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass)) + +typedef struct _EggSMClientXSMP EggSMClientXSMP; +typedef struct _EggSMClientXSMPClass EggSMClientXSMPClass; + +/* These mostly correspond to the similarly-named states in section + * 9.1 of the XSMP spec. Some of the states there aren't represented + * here, because we don't need them. SHUTDOWN_CANCELLED is slightly + * different from the spec; we use it when the client is IDLE after a + * ShutdownCancelled message, but the application is still interacting + * and doesn't know the shutdown has been cancelled yet. + */ +typedef enum +{ + XSMP_STATE_IDLE, + XSMP_STATE_SAVE_YOURSELF, + XSMP_STATE_INTERACT_REQUEST, + XSMP_STATE_INTERACT, + XSMP_STATE_SAVE_YOURSELF_DONE, + XSMP_STATE_SHUTDOWN_CANCELLED, + XSMP_STATE_CONNECTION_CLOSED +} EggSMClientXSMPState; + +static const char *state_names[] = { + "idle", + "save-yourself", + "interact-request", + "interact", + "save-yourself-done", + "shutdown-cancelled", + "connection-closed" +}; + +#define EGG_SM_CLIENT_XSMP_STATE(xsmp) (state_names[(xsmp)->state]) + +struct _EggSMClientXSMP +{ + EggSMClient parent; + + SmcConn connection; + char *client_id; + + EggSMClientXSMPState state; + char **restart_command; + gboolean set_restart_command; + int restart_style; + + guint idle; + + /* Current SaveYourself state */ + guint expecting_initial_save_yourself : 1; + guint need_save_state : 1; + guint need_quit_requested : 1; + guint interact_errors : 1; + guint shutting_down : 1; + + /* Todo list */ + guint waiting_to_set_initial_properties : 1; + guint waiting_to_emit_quit : 1; + guint waiting_to_emit_quit_cancelled : 1; + guint waiting_to_save_myself : 1; + +}; + +struct _EggSMClientXSMPClass +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_xsmp_startup (EggSMClient *client, + const char *client_id); +static void sm_client_xsmp_set_restart_command (EggSMClient *client, + int argc, + const char **argv); +static void sm_client_xsmp_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_xsmp_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static void xsmp_save_yourself (SmcConn smc_conn, + SmPointer client_data, + int save_style, + Bool shutdown, + int interact_style, + Bool fast); +static void xsmp_die (SmcConn smc_conn, + SmPointer client_data); +static void xsmp_save_complete (SmcConn smc_conn, + SmPointer client_data); +static void xsmp_shutdown_cancelled (SmcConn smc_conn, + SmPointer client_data); +static void xsmp_interact (SmcConn smc_conn, + SmPointer client_data); + +static SmProp *array_prop (const char *name, + ...); +static SmProp *ptrarray_prop (const char *name, + GPtrArray *values); +static SmProp *string_prop (const char *name, + const char *value); +static SmProp *card8_prop (const char *name, + unsigned char value); + +static void set_properties (EggSMClientXSMP *xsmp, ...); +static void delete_properties (EggSMClientXSMP *xsmp, ...); + +static GPtrArray *generate_command (char **restart_command, + const char *client_id, + const char *state_file); + +static void save_state (EggSMClientXSMP *xsmp); +static void do_save_yourself (EggSMClientXSMP *xsmp); +static void update_pending_events (EggSMClientXSMP *xsmp); + +static void ice_init (void); +static gboolean process_ice_messages (IceConn ice_conn); +static void smc_error_handler (SmcConn smc_conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + SmPointer values); + +G_DEFINE_TYPE (EggSMClientXSMP, egg_sm_client_xsmp, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp) +{ + xsmp->state = XSMP_STATE_CONNECTION_CLOSED; + xsmp->connection = NULL; + xsmp->restart_style = SmRestartIfRunning; +} + +static void +egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_xsmp_startup; + sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command; + sm_client_class->will_quit = sm_client_xsmp_will_quit; + sm_client_class->end_session = sm_client_xsmp_end_session; +} + +EggSMClient * +egg_sm_client_xsmp_new (void) +{ + if (!g_getenv ("SESSION_MANAGER")) + return NULL; + + return g_object_new (EGG_TYPE_SM_CLIENT_XSMP, NULL); +} + +static gboolean +sm_client_xsmp_set_initial_properties (gpointer user_data) +{ + EggSMClientXSMP *xsmp = user_data; + EggDesktopFile *desktop_file; + GPtrArray *clone, *restart; + char pid_str[64]; + + if (xsmp->idle) + { + g_source_remove (xsmp->idle); + xsmp->idle = 0; + } + xsmp->waiting_to_set_initial_properties = FALSE; + + if (egg_sm_client_get_mode () == EGG_SM_CLIENT_MODE_NO_RESTART) + xsmp->restart_style = SmRestartNever; + + /* Parse info out of desktop file */ + desktop_file = egg_get_desktop_file (); + if (desktop_file) + { + GError *err = NULL; + char *cmdline, **argv; + int argc; + + if (xsmp->restart_style == SmRestartIfRunning) + { + if (egg_desktop_file_get_boolean (desktop_file, + "X-MATE-AutoRestart", NULL)) + xsmp->restart_style = SmRestartImmediately; + } + + if (!xsmp->set_restart_command) + { + cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err); + if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err)) + { + egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp), + argc, (const char **)argv); + g_strfreev (argv); + } + else + { + g_warning ("Could not parse Exec line in desktop file: %s", + err->message); + g_error_free (err); + } + g_free (cmdline); + } + } + + if (!xsmp->set_restart_command) + xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1); + + clone = generate_command (xsmp->restart_command, NULL, NULL); + restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL); + + g_debug ("Setting initial properties"); + + /* Program, CloneCommand, RestartCommand, and UserID are required. + * ProcessID isn't required, but the SM may be able to do something + * useful with it. + */ + g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ()); + set_properties (xsmp, + string_prop (SmProgram, g_get_prgname ()), + ptrarray_prop (SmCloneCommand, clone), + ptrarray_prop (SmRestartCommand, restart), + string_prop (SmUserID, g_get_user_name ()), + string_prop (SmProcessID, pid_str), + card8_prop (SmRestartStyleHint, xsmp->restart_style), + NULL); + g_ptr_array_free (clone, TRUE); + g_ptr_array_free (restart, TRUE); + + if (desktop_file) + { + set_properties (xsmp, + string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)), + NULL); + } + + update_pending_events (xsmp); + return FALSE; +} + +/* This gets called from two different places: xsmp_die() (when the + * server asks us to disconnect) and process_ice_messages() (when the + * server disconnects unexpectedly). + */ +static void +sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp) +{ + SmcConn connection; + + if (!xsmp->connection) + return; + + g_debug ("Disconnecting"); + + connection = xsmp->connection; + xsmp->connection = NULL; + SmcCloseConnection (connection, 0, NULL); + xsmp->state = XSMP_STATE_CONNECTION_CLOSED; + + xsmp->waiting_to_save_myself = FALSE; + update_pending_events (xsmp); +} + +static void +sm_client_xsmp_startup (EggSMClient *client, + const char *client_id) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + SmcCallbacks callbacks; + char *ret_client_id; + char error_string_ret[256]; + + xsmp->client_id = g_strdup (client_id); + + ice_init (); + SmcSetErrorHandler (smc_error_handler); + + callbacks.save_yourself.callback = xsmp_save_yourself; + callbacks.die.callback = xsmp_die; + callbacks.save_complete.callback = xsmp_save_complete; + callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled; + + callbacks.save_yourself.client_data = xsmp; + callbacks.die.client_data = xsmp; + callbacks.save_complete.client_data = xsmp; + callbacks.shutdown_cancelled.client_data = xsmp; + + client_id = NULL; + error_string_ret[0] = '\0'; + xsmp->connection = + SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor, + SmcSaveYourselfProcMask | SmcDieProcMask | + SmcSaveCompleteProcMask | + SmcShutdownCancelledProcMask, + &callbacks, + xsmp->client_id, &ret_client_id, + sizeof (error_string_ret), error_string_ret); + + if (!xsmp->connection) + { + g_warning ("Failed to connect to the session manager: %s\n", + error_string_ret[0] ? + error_string_ret : "no error message given"); + xsmp->state = XSMP_STATE_CONNECTION_CLOSED; + return; + } + + /* We expect a pointless initial SaveYourself if either (a) we + * didn't have an initial client ID, or (b) we DID have an initial + * client ID, but the server rejected it and gave us a new one. + */ + if (!xsmp->client_id || + (ret_client_id && strcmp (xsmp->client_id, ret_client_id) != 0)) + xsmp->expecting_initial_save_yourself = TRUE; + + if (ret_client_id) + { + g_free (xsmp->client_id); + xsmp->client_id = g_strdup (ret_client_id); + free (ret_client_id); + + gdk_threads_enter (); + gdk_set_sm_client_id (xsmp->client_id); + gdk_threads_leave (); + + g_debug ("Got client ID \"%s\"", xsmp->client_id); + } + + xsmp->state = XSMP_STATE_IDLE; + + /* Do not set the initial properties until we reach the main loop, + * so that the application has a chance to call + * egg_set_desktop_file(). (This may also help the session manager + * have a better idea of when the application is fully up and + * running.) + */ + xsmp->waiting_to_set_initial_properties = TRUE; + xsmp->idle = g_idle_add (sm_client_xsmp_set_initial_properties, client); +} + +static void +sm_client_xsmp_set_restart_command (EggSMClient *client, + int argc, + const char **argv) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + int i; + + g_strfreev (xsmp->restart_command); + + xsmp->restart_command = g_new (char *, argc + 1); + for (i = 0; i < argc; i++) + xsmp->restart_command[i] = g_strdup (argv[i]); + xsmp->restart_command[i] = NULL; + + xsmp->set_restart_command = TRUE; +} + +static void +sm_client_xsmp_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + + if (xsmp->state == XSMP_STATE_CONNECTION_CLOSED) + { + /* The session manager has already exited! Schedule a quit + * signal. + */ + xsmp->waiting_to_emit_quit = TRUE; + update_pending_events (xsmp); + return; + } + else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) + { + /* We received a ShutdownCancelled message while the application + * was interacting; Schedule a quit_cancelled signal. + */ + xsmp->waiting_to_emit_quit_cancelled = TRUE; + update_pending_events (xsmp); + return; + } + + g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT); + + g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True"); + SmcInteractDone (xsmp->connection, !will_quit); + + if (will_quit && xsmp->need_save_state) + save_state (xsmp); + + g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False"); + SmcSaveYourselfDone (xsmp->connection, will_quit); + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; +} + +static gboolean +sm_client_xsmp_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation) +{ + EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + int save_type; + + /* To end the session via XSMP, we have to send a + * SaveYourselfRequest. We aren't allowed to do that if anything + * else is going on, but we don't want to expose this fact to the + * application. So we do our best to patch things up here... + * + * In the worst case, this method might block for some length of + * time in process_ice_messages, but the only time that code path is + * honestly likely to get hit is if the application tries to end the + * session as the very first thing it does, in which case it + * probably won't actually block anyway. It's not worth gunking up + * the API to try to deal nicely with the other 0.01% of cases where + * this happens. + */ + + while (xsmp->state != XSMP_STATE_IDLE || + xsmp->expecting_initial_save_yourself) + { + /* If we're already shutting down, we don't need to do anything. */ + if (xsmp->shutting_down) + return TRUE; + + switch (xsmp->state) + { + case XSMP_STATE_CONNECTION_CLOSED: + return FALSE; + + case XSMP_STATE_SAVE_YOURSELF: + /* Trying to log out from the save_state callback? Whatever. + * Abort the save_state. + */ + SmcSaveYourselfDone (xsmp->connection, FALSE); + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; + break; + + case XSMP_STATE_INTERACT_REQUEST: + case XSMP_STATE_INTERACT: + case XSMP_STATE_SHUTDOWN_CANCELLED: + /* Already in a shutdown-related state, just ignore + * the new shutdown request... + */ + return TRUE; + + case XSMP_STATE_IDLE: + if (xsmp->waiting_to_set_initial_properties) + sm_client_xsmp_set_initial_properties (xsmp); + + if (!xsmp->expecting_initial_save_yourself) + break; + /* else fall through */ + + case XSMP_STATE_SAVE_YOURSELF_DONE: + /* We need to wait for some response from the server.*/ + process_ice_messages (SmcGetIceConnection (xsmp->connection)); + break; + + default: + /* Hm... shouldn't happen */ + return FALSE; + } + } + + /* xfce4-session will do the wrong thing if we pass SmSaveGlobal and + * the user chooses to save the session. But mate-session will do + * the wrong thing if we pass SmSaveBoth and the user chooses NOT to + * save the session... Sigh. + */ + if (!strcmp (SmcVendor (xsmp->connection), "xfce4-session")) + save_type = SmSaveBoth; + else + save_type = SmSaveGlobal; + + g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : ""); + SmcRequestSaveYourself (xsmp->connection, + save_type, + True, /* shutdown */ + SmInteractStyleAny, + !request_confirmation, /* fast */ + True /* global */); + return TRUE; +} + +static gboolean +idle_do_pending_events (gpointer data) +{ + EggSMClientXSMP *xsmp = data; + EggSMClient *client = data; + + gdk_threads_enter (); + + xsmp->idle = 0; + + if (xsmp->waiting_to_emit_quit) + { + xsmp->waiting_to_emit_quit = FALSE; + egg_sm_client_quit (client); + goto out; + } + + if (xsmp->waiting_to_emit_quit_cancelled) + { + xsmp->waiting_to_emit_quit_cancelled = FALSE; + egg_sm_client_quit_cancelled (client); + xsmp->state = XSMP_STATE_IDLE; + } + + if (xsmp->waiting_to_save_myself) + { + xsmp->waiting_to_save_myself = FALSE; + do_save_yourself (xsmp); + } + + out: + gdk_threads_leave (); + return FALSE; +} + +static void +update_pending_events (EggSMClientXSMP *xsmp) +{ + gboolean want_idle = + xsmp->waiting_to_emit_quit || + xsmp->waiting_to_emit_quit_cancelled || + xsmp->waiting_to_save_myself; + + if (want_idle) + { + if (xsmp->idle == 0) + xsmp->idle = g_idle_add (idle_do_pending_events, xsmp); + } + else + { + if (xsmp->idle != 0) + g_source_remove (xsmp->idle); + xsmp->idle = 0; + } +} + +static void +fix_broken_state (EggSMClientXSMP *xsmp, const char *message, + gboolean send_interact_done, + gboolean send_save_yourself_done) +{ + g_warning ("Received XSMP %s message in state %s: client or server error", + message, EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + /* Forget any pending SaveYourself plans we had */ + xsmp->waiting_to_save_myself = FALSE; + update_pending_events (xsmp); + + if (send_interact_done) + SmcInteractDone (xsmp->connection, False); + if (send_save_yourself_done) + SmcSaveYourselfDone (xsmp->connection, True); + + xsmp->state = send_save_yourself_done ? XSMP_STATE_SAVE_YOURSELF_DONE : XSMP_STATE_IDLE; +} + +/* SM callbacks */ + +static void +xsmp_save_yourself (SmcConn smc_conn, + SmPointer client_data, + int save_type, + Bool shutdown, + int interact_style, + Bool fast) +{ + EggSMClientXSMP *xsmp = client_data; + gboolean wants_quit_requested; + + g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s", + save_type == SmSaveLocal ? "SmSaveLocal" : + save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth", + shutdown ? "Shutdown" : "!Shutdown", + interact_style == SmInteractStyleAny ? "SmInteractStyleAny" : + interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" : + "SmInteractStyleNone", fast ? "Fast" : "!Fast", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + if (xsmp->state != XSMP_STATE_IDLE && + xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED) + { + fix_broken_state (xsmp, "SaveYourself", FALSE, TRUE); + return; + } + + if (xsmp->waiting_to_set_initial_properties) + sm_client_xsmp_set_initial_properties (xsmp); + + /* If this is the initial SaveYourself, ignore it; we've already set + * properties and there's no reason to actually save state too. + */ + if (xsmp->expecting_initial_save_yourself) + { + xsmp->expecting_initial_save_yourself = FALSE; + + if (save_type == SmSaveLocal && + interact_style == SmInteractStyleNone && + !shutdown && !fast) + { + g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself"); + SmcSaveYourselfDone (xsmp->connection, True); + /* As explained in the comment at the end of + * do_save_yourself(), SAVE_YOURSELF_DONE is the correct + * state here, not IDLE. + */ + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; + return; + } + else + g_warning ("First SaveYourself was not the expected one!"); + } + + /* Even ignoring the "fast" flag completely, there are still 18 + * different combinations of save_type, shutdown and interact_style. + * We interpret them as follows: + * + * Type Shutdown Interact Interpretation + * G F A/E/N do nothing (1) + * G T N do nothing (1)* + * G T A/E quit_requested (2) + * L/B F A/E/N save_state (3) + * L/B T N save_state (3)* + * L/B T A/E quit_requested, then save_state (4) + * + * 1. Do nothing, because the SM asked us to do something + * uninteresting (save open files, but then don't quit + * afterward) or rude (save open files without asking the user + * for confirmation). + * + * 2. Request interaction and then emit ::quit_requested. This + * perhaps isn't quite correct for the SmInteractStyleErrors + * case, but we don't care. + * + * 3. Emit ::save_state. The SmSaveBoth SaveYourselfs in these + * rows essentially get demoted to SmSaveLocal, because their + * Global halves correspond to "do nothing". + * + * 4. Request interaction, emit ::quit_requested, and then emit + * ::save_state after interacting. This is the SmSaveBoth + * equivalent of #2, but we also promote SmSaveLocal shutdown + * SaveYourselfs to SmSaveBoth here, because we want to give + * the user a chance to save open files before quitting. + * + * (* It would be nice if we could do something useful when the + * session manager sends a SaveYourself with shutdown True and + * SmInteractStyleNone. But we can't, so we just pretend it didn't + * even tell us it was shutting down. The docs for ::quit mention + * that it might not always be preceded by ::quit_requested.) + */ + + /* As an optimization, we don't actually request interaction and + * emit ::quit_requested if the application isn't listening to the + * signal. + */ + wants_quit_requested = g_signal_has_handler_pending (xsmp, g_signal_lookup ("quit_requested", EGG_TYPE_SM_CLIENT), 0, FALSE); + + xsmp->need_save_state = (save_type != SmSaveGlobal); + xsmp->need_quit_requested = (shutdown && wants_quit_requested && + interact_style != SmInteractStyleNone); + xsmp->interact_errors = (interact_style == SmInteractStyleErrors); + + xsmp->shutting_down = shutdown; + + do_save_yourself (xsmp); +} + +static void +do_save_yourself (EggSMClientXSMP *xsmp) +{ + if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) + { + /* The SM cancelled a previous SaveYourself, but we haven't yet + * had a chance to tell the application, so we can't start + * processing this SaveYourself yet. + */ + xsmp->waiting_to_save_myself = TRUE; + update_pending_events (xsmp); + return; + } + + if (xsmp->need_quit_requested) + { + xsmp->state = XSMP_STATE_INTERACT_REQUEST; + + g_debug ("Sending InteractRequest(%s)", + xsmp->interact_errors ? "Error" : "Normal"); + SmcInteractRequest (xsmp->connection, + xsmp->interact_errors ? SmDialogError : SmDialogNormal, + xsmp_interact, + xsmp); + return; + } + + if (xsmp->need_save_state) + { + save_state (xsmp); + + /* Though unlikely, the client could have been disconnected + * while the application was saving its state. + */ + if (!xsmp->connection) + return; + } + + g_debug ("Sending SaveYourselfDone(True)"); + SmcSaveYourselfDone (xsmp->connection, True); + + /* The client state diagram in the XSMP spec says that after a + * non-shutdown SaveYourself, we go directly back to "idle". But + * everything else in both the XSMP spec and the libSM docs + * disagrees. + */ + xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; +} + +static void +save_state (EggSMClientXSMP *xsmp) +{ + GKeyFile *state_file; + char *state_file_path, *data; + EggDesktopFile *desktop_file; + GPtrArray *restart; + int offset, fd; + + /* We set xsmp->state before emitting save_state, but our caller is + * responsible for setting it back afterward. + */ + xsmp->state = XSMP_STATE_SAVE_YOURSELF; + + state_file = egg_sm_client_save_state ((EggSMClient *)xsmp); + if (!state_file) + { + restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL); + set_properties (xsmp, + ptrarray_prop (SmRestartCommand, restart), + NULL); + g_ptr_array_free (restart, TRUE); + delete_properties (xsmp, SmDiscardCommand, NULL); + return; + } + + desktop_file = egg_get_desktop_file (); + if (desktop_file) + { + GKeyFile *merged_file; + char *desktop_file_path; + + merged_file = g_key_file_new (); + desktop_file_path = + g_filename_from_uri (egg_desktop_file_get_source (desktop_file), + NULL, NULL); + if (desktop_file_path && + g_key_file_load_from_file (merged_file, desktop_file_path, + G_KEY_FILE_KEEP_COMMENTS | + G_KEY_FILE_KEEP_TRANSLATIONS, NULL)) + { + guint g, k, i; + char **groups, **keys, *value, *exec; + + groups = g_key_file_get_groups (state_file, NULL); + for (g = 0; groups[g]; g++) + { + keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL); + for (k = 0; keys[k]; k++) + { + value = g_key_file_get_value (state_file, groups[g], + keys[k], NULL); + if (value) + { + g_key_file_set_value (merged_file, groups[g], + keys[k], value); + g_free (value); + } + } + g_strfreev (keys); + } + g_strfreev (groups); + + g_key_file_free (state_file); + state_file = merged_file; + + /* Update Exec key using "--sm-client-state-file %k" */ + restart = generate_command (xsmp->restart_command, + NULL, "%k"); + for (i = 0; i < restart->len; i++) + restart->pdata[i] = g_shell_quote (restart->pdata[i]); + g_ptr_array_add (restart, NULL); + exec = g_strjoinv (" ", (char **)restart->pdata); + g_strfreev ((char **)restart->pdata); + g_ptr_array_free (restart, FALSE); + + g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + exec); + g_free (exec); + } + else + desktop_file = NULL; + + g_free (desktop_file_path); + } + + /* Now write state_file to disk. (We can't use mktemp(), because + * that requires the filename to end with "XXXXXX", and we want + * it to end with ".desktop".) + */ + + data = g_key_file_to_data (state_file, NULL, NULL); + g_key_file_free (state_file); + + offset = 0; + while (1) + { + state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s", + g_get_user_config_dir (), + G_DIR_SEPARATOR, G_DIR_SEPARATOR, + g_get_prgname (), + (long)time (NULL) + offset, + desktop_file ? "desktop" : "state"); + + fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd == -1) + { + if (errno == EEXIST) + { + offset++; + g_free (state_file_path); + continue; + } + else if (errno == ENOTDIR || errno == ENOENT) + { + char *sep = strrchr (state_file_path, G_DIR_SEPARATOR); + + *sep = '\0'; + if (g_mkdir_with_parents (state_file_path, 0755) != 0) + { + g_warning ("Could not create directory '%s'", + state_file_path); + g_free (state_file_path); + state_file_path = NULL; + break; + } + + continue; + } + + g_warning ("Could not create file '%s': %s", + state_file_path, g_strerror (errno)); + g_free (state_file_path); + state_file_path = NULL; + break; + } + + close (fd); + g_file_set_contents (state_file_path, data, -1, NULL); + break; + } + g_free (data); + + restart = generate_command (xsmp->restart_command, xsmp->client_id, + state_file_path); + set_properties (xsmp, + ptrarray_prop (SmRestartCommand, restart), + NULL); + g_ptr_array_free (restart, TRUE); + + if (state_file_path) + { + set_properties (xsmp, + array_prop (SmDiscardCommand, + "/bin/rm", "-rf", state_file_path, + NULL), + NULL); + g_free (state_file_path); + } +} + +static void +xsmp_interact (SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + EggSMClient *client = client_data; + + g_debug ("Received Interact message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + if (xsmp->state != XSMP_STATE_INTERACT_REQUEST) + { + fix_broken_state (xsmp, "Interact", TRUE, TRUE); + return; + } + + xsmp->state = XSMP_STATE_INTERACT; + egg_sm_client_quit_requested (client); +} + +static void +xsmp_die (SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + EggSMClient *client = client_data; + + g_debug ("Received Die message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + sm_client_xsmp_disconnect (xsmp); + egg_sm_client_quit (client); +} + +static void +xsmp_save_complete (SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + + g_debug ("Received SaveComplete message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE) + xsmp->state = XSMP_STATE_IDLE; + else + fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE); +} + +static void +xsmp_shutdown_cancelled (SmcConn smc_conn, + SmPointer client_data) +{ + EggSMClientXSMP *xsmp = client_data; + EggSMClient *client = client_data; + + g_debug ("Received ShutdownCancelled message in state %s", + EGG_SM_CLIENT_XSMP_STATE (xsmp)); + + xsmp->shutting_down = FALSE; + + if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE) + { + /* We've finished interacting and now the SM has agreed to + * cancel the shutdown. + */ + xsmp->state = XSMP_STATE_IDLE; + egg_sm_client_quit_cancelled (client); + } + else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) + { + /* Hm... ok, so we got a shutdown SaveYourself, which got + * cancelled, but the application was still interacting, so we + * didn't tell it yet, and then *another* SaveYourself arrived, + * which we must still be waiting to tell the app about, except + * that now that SaveYourself has been cancelled too! Dizzy yet? + */ + xsmp->waiting_to_save_myself = FALSE; + update_pending_events (xsmp); + } + else + { + g_debug ("Sending SaveYourselfDone(False)"); + SmcSaveYourselfDone (xsmp->connection, False); + + if (xsmp->state == XSMP_STATE_INTERACT) + { + /* The application is currently interacting, so we can't + * tell it about the cancellation yet; we will wait until + * after it calls egg_sm_client_will_quit(). + */ + xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED; + } + else + { + /* The shutdown was cancelled before the application got a + * chance to interact. + */ + xsmp->state = XSMP_STATE_IDLE; + } + } +} + +/* Utilities */ + +/* Create a restart/clone/Exec command based on @restart_command. + * If @client_id is non-%NULL, add "--sm-client-id @client_id". + * If @state_file is non-%NULL, add "--sm-client-state-file @state_file". + * + * None of the input strings are g_strdup()ed; the caller must keep + * them around until it is done with the returned GPtrArray, and must + * then free the array, but not its contents. + */ +static GPtrArray * +generate_command (char **restart_command, const char *client_id, + const char *state_file) +{ + GPtrArray *cmd; + int i; + + cmd = g_ptr_array_new (); + g_ptr_array_add (cmd, restart_command[0]); + + if (client_id) + { + g_ptr_array_add (cmd, "--sm-client-id"); + g_ptr_array_add (cmd, (char *)client_id); + } + + if (state_file) + { + g_ptr_array_add (cmd, "--sm-client-state-file"); + g_ptr_array_add (cmd, (char *)state_file); + } + + for (i = 1; restart_command[i]; i++) + g_ptr_array_add (cmd, restart_command[i]); + + return cmd; +} + +/* Takes a NULL-terminated list of SmProp * values, created by + * array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and + * frees them. + */ +static void +set_properties (EggSMClientXSMP *xsmp, ...) +{ + GPtrArray *props; + SmProp *prop; + va_list ap; + guint i; + + props = g_ptr_array_new (); + + va_start (ap, xsmp); + while ((prop = va_arg (ap, SmProp *))) + g_ptr_array_add (props, prop); + va_end (ap); + + if (xsmp->connection) + { + SmcSetProperties (xsmp->connection, props->len, + (SmProp **)props->pdata); + } + + for (i = 0; i < props->len; i++) + { + prop = props->pdata[i]; + g_free (prop->vals); + g_free (prop); + } + g_ptr_array_free (props, TRUE); +} + +/* Takes a NULL-terminated list of property names and deletes them. */ +static void +delete_properties (EggSMClientXSMP *xsmp, ...) +{ + GPtrArray *props; + char *prop; + va_list ap; + + if (!xsmp->connection) + return; + + props = g_ptr_array_new (); + + va_start (ap, xsmp); + while ((prop = va_arg (ap, char *))) + g_ptr_array_add (props, prop); + va_end (ap); + + SmcDeleteProperties (xsmp->connection, props->len, + (char **)props->pdata); + + g_ptr_array_free (props, TRUE); +} + +/* Takes an array of strings and creates a LISTofARRAY8 property. The + * strings are neither dupped nor freed; they need to remain valid + * until you're done with the SmProp. + */ +static SmProp * +array_prop (const char *name, ...) +{ + SmProp *prop; + SmPropValue pv; + GArray *vals; + char *value; + va_list ap; + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = SmLISTofARRAY8; + + vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue)); + + va_start (ap, name); + while ((value = va_arg (ap, char *))) + { + pv.length = strlen (value); + pv.value = value; + g_array_append_val (vals, pv); + } + + prop->num_vals = vals->len; + prop->vals = (SmPropValue *)vals->data; + + g_array_free (vals, FALSE); + + return prop; +} + +/* Takes a GPtrArray of strings and creates a LISTofARRAY8 property. + * The array contents are neither dupped nor freed; they need to + * remain valid until you're done with the SmProp. + */ +static SmProp * +ptrarray_prop (const char *name, GPtrArray *values) +{ + SmProp *prop; + SmPropValue pv; + GArray *vals; + guint i; + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = SmLISTofARRAY8; + + vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue)); + + for (i = 0; i < values->len; i++) + { + pv.length = strlen (values->pdata[i]); + pv.value = values->pdata[i]; + g_array_append_val (vals, pv); + } + + prop->num_vals = vals->len; + prop->vals = (SmPropValue *)vals->data; + + g_array_free (vals, FALSE); + + return prop; +} + +/* Takes a string and creates an ARRAY8 property. The string is + * neither dupped nor freed; it needs to remain valid until you're + * done with the SmProp. + */ +static SmProp * +string_prop (const char *name, const char *value) +{ + SmProp *prop; + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = SmARRAY8; + + prop->num_vals = 1; + prop->vals = g_new (SmPropValue, 1); + + prop->vals[0].length = strlen (value); + prop->vals[0].value = (char *)value; + + return prop; +} + +/* Takes a char and creates a CARD8 property. */ +static SmProp * +card8_prop (const char *name, unsigned char value) +{ + SmProp *prop; + char *card8val; + + /* To avoid having to allocate and free prop->vals[0], we cheat and + * make vals a 2-element-long array and then use the second element + * to store value. + */ + + prop = g_new (SmProp, 1); + prop->name = (char *)name; + prop->type = SmCARD8; + + prop->num_vals = 1; + prop->vals = g_new (SmPropValue, 2); + card8val = (char *)(&prop->vals[1]); + card8val[0] = value; + + prop->vals[0].length = 1; + prop->vals[0].value = card8val; + + return prop; +} + +/* ICE code. This makes no effort to play nice with anyone else trying + * to use libICE. Fortunately, no one uses libICE for anything other + * than SM. (DCOP uses ICE, but it has its own private copy of + * libICE.) + * + * When this moves to gtk, it will need to be cleverer, to avoid + * tripping over old apps that use MateClient or that use libSM + * directly. + */ + +#include +#include + +static void ice_error_handler (IceConn ice_conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + IcePointer values); +static void ice_io_error_handler (IceConn ice_conn); +static void ice_connection_watch (IceConn ice_conn, + IcePointer client_data, + Bool opening, + IcePointer *watch_data); + +static void +ice_init (void) +{ + IceSetIOErrorHandler (ice_io_error_handler); + IceSetErrorHandler (ice_error_handler); + IceAddConnectionWatch (ice_connection_watch, NULL); +} + +static gboolean +process_ice_messages (IceConn ice_conn) +{ + IceProcessMessagesStatus status; + + gdk_threads_enter (); + status = IceProcessMessages (ice_conn, NULL, NULL); + gdk_threads_leave (); + + switch (status) + { + case IceProcessMessagesSuccess: + return TRUE; + + case IceProcessMessagesIOError: + sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn)); + return FALSE; + + case IceProcessMessagesConnectionClosed: + return FALSE; + + default: + g_assert_not_reached (); + } +} + +static gboolean +ice_iochannel_watch (GIOChannel *channel, + GIOCondition condition, + gpointer client_data) +{ + return process_ice_messages (client_data); +} + +static void +ice_connection_watch (IceConn ice_conn, + IcePointer client_data, + Bool opening, + IcePointer *watch_data) +{ + guint watch_id; + + if (opening) + { + GIOChannel *channel; + int fd = IceConnectionNumber (ice_conn); + + fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC); + channel = g_io_channel_unix_new (fd); + watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR, + ice_iochannel_watch, ice_conn); + g_io_channel_unref (channel); + + *watch_data = GUINT_TO_POINTER (watch_id); + } + else + { + watch_id = GPOINTER_TO_UINT (*watch_data); + g_source_remove (watch_id); + } +} + +static void +ice_error_handler (IceConn ice_conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + IcePointer values) +{ + /* Do nothing */ +} + +static void +ice_io_error_handler (IceConn ice_conn) +{ + /* Do nothing */ +} + +static void +smc_error_handler (SmcConn smc_conn, + Bool swap, + int offending_minor_opcode, + unsigned long offending_sequence, + int error_class, + int severity, + SmPointer values) +{ + /* Do nothing */ +} diff --git a/cut-n-paste/smclient/eggsmclient.c b/cut-n-paste/smclient/eggsmclient.c new file mode 100644 index 00000000..c568e9be --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient.c @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include +#include + +#include "eggsmclient.h" +#include "eggsmclient-private.h" + +static void egg_sm_client_debug_handler (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + gpointer user_data); + +enum { + SAVE_STATE, + QUIT_REQUESTED, + QUIT_CANCELLED, + QUIT, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +struct _EggSMClientPrivate { + GKeyFile *state_file; +}; + +#define EGG_SM_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_SM_CLIENT, EggSMClientPrivate)) + +G_DEFINE_TYPE (EggSMClient, egg_sm_client, G_TYPE_OBJECT) + +static EggSMClient *global_client; +static EggSMClientMode global_client_mode = EGG_SM_CLIENT_MODE_NORMAL; + +static void +egg_sm_client_init (EggSMClient *client) +{ + ; +} + +static void +egg_sm_client_class_init (EggSMClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (EggSMClientPrivate)); + + /** + * EggSMClient::save_state: + * @client: the client + * @state_file: a #GKeyFile to save state information into + * + * Emitted when the session manager has requested that the + * application save information about its current state. The + * application should save its state into @state_file, and then the + * session manager may then restart the application in a future + * session and tell it to initialize itself from that state. + * + * You should not save any data into @state_file's "start group" + * (ie, the %NULL group). Instead, applications should save their + * data into groups with names that start with the application name, + * and libraries that connect to this signal should save their data + * into groups with names that start with the library name. + * + * Alternatively, rather than (or in addition to) using @state_file, + * the application can save its state by calling + * egg_sm_client_set_restart_command() during the processing of this + * signal (eg, to include a list of files to open). + **/ + signals[SAVE_STATE] = + g_signal_new ("save_state", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, save_state), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * EggSMClient::quit_requested: + * @client: the client + * + * Emitted when the session manager requests that the application + * exit (generally because the user is logging out). The application + * should decide whether or not it is willing to quit (perhaps after + * asking the user what to do with documents that have unsaved + * changes) and then call egg_sm_client_will_quit(), passing %TRUE + * or %FALSE to give its answer to the session manager. (It does not + * need to give an answer before returning from the signal handler; + * it can interact with the user asynchronously and then give its + * answer later on.) If the application does not connect to this + * signal, then #EggSMClient will automatically return %TRUE on its + * behalf. + * + * The application should not save its session state as part of + * handling this signal; if the user has requested that the session + * be saved when logging out, then ::save_state will be emitted + * separately. + * + * If the application agrees to quit, it should then wait for either + * the ::quit_cancelled or ::quit signals to be emitted. + **/ + signals[QUIT_REQUESTED] = + g_signal_new ("quit_requested", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, quit_requested), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * EggSMClient::quit_cancelled: + * @client: the client + * + * Emitted when the session manager decides to cancel a logout after + * the application has already agreed to quit. After receiving this + * signal, the application can go back to what it was doing before + * receiving the ::quit_requested signal. + **/ + signals[QUIT_CANCELLED] = + g_signal_new ("quit_cancelled", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, quit_cancelled), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * EggSMClient::quit: + * @client: the client + * + * Emitted when the session manager wants the application to quit + * (generally because the user is logging out). The application + * should exit as soon as possible after receiving this signal; if + * it does not, the session manager may choose to forcibly kill it. + * + * Normally a GUI application would only be sent a ::quit if it + * agreed to quit in response to a ::quit_requested signal. However, + * this is not guaranteed; in some situations the session manager + * may decide to end the session without giving applications a + * chance to object. + **/ + signals[QUIT] = + g_signal_new ("quit", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggSMClientClass, quit), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +static gboolean sm_client_disable = FALSE; +static char *sm_client_state_file = NULL; +static char *sm_client_id = NULL; +static char *sm_config_prefix = NULL; + +static gboolean +sm_client_post_parse_func (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error) +{ + EggSMClient *client = egg_sm_client_get (); + + if (sm_client_id == NULL) + { + const gchar *desktop_autostart_id; + + desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + + if (desktop_autostart_id != NULL) + sm_client_id = g_strdup (desktop_autostart_id); + } + + /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to + * use the same client id. */ + g_unsetenv ("DESKTOP_AUTOSTART_ID"); + + if (EGG_SM_CLIENT_GET_CLASS (client)->startup) + EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id); + return TRUE; +} + +/** + * egg_sm_client_get_option_group: + * + * Creates a %GOptionGroup containing the session-management-related + * options. You should add this group to the application's + * %GOptionContext if you want to use #EggSMClient. + * + * Return value: the %GOptionGroup + **/ +GOptionGroup * +egg_sm_client_get_option_group (void) +{ + const GOptionEntry entries[] = { + { "sm-client-disable", 0, 0, + G_OPTION_ARG_NONE, &sm_client_disable, + N_("Disable connection to session manager"), NULL }, + { "sm-client-state-file", 0, 0, + G_OPTION_ARG_FILENAME, &sm_client_state_file, + N_("Specify file containing saved configuration"), N_("FILE") }, + { "sm-client-id", 0, 0, + G_OPTION_ARG_STRING, &sm_client_id, + N_("Specify session management ID"), N_("ID") }, + /* MateClient compatibility option */ + { "sm-disable", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_NONE, &sm_client_disable, + NULL, NULL }, + /* MateClient compatibility option. This is a dummy option that only + * exists so that sessions saved by apps with MateClient can be restored + * later when they've switched to EggSMClient. See bug #575308. + */ + { "sm-config-prefix", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, &sm_config_prefix, + NULL, NULL }, + { NULL } + }; + GOptionGroup *group; + + /* Use our own debug handler for the "EggSMClient" domain. */ + g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, + egg_sm_client_debug_handler, NULL); + + group = g_option_group_new ("sm-client", + _("Session management options:"), + _("Show session management options"), + NULL, NULL); + g_option_group_add_entries (group, entries); + g_option_group_set_parse_hooks (group, NULL, sm_client_post_parse_func); + + return group; +} + +/** + * egg_sm_client_set_mode: + * @mode: an #EggSMClient mode + * + * Sets the "mode" of #EggSMClient as follows: + * + * %EGG_SM_CLIENT_MODE_DISABLED: Session management is completely + * disabled. The application will not even connect to the session + * manager. (egg_sm_client_get() will still return an #EggSMClient, + * but it will just be a dummy object.) + * + * %EGG_SM_CLIENT_MODE_NO_RESTART: The application will connect to + * the session manager (and thus will receive notification when the + * user is logging out, etc), but will request to not be + * automatically restarted with saved state in future sessions. + * + * %EGG_SM_CLIENT_MODE_NORMAL: The default. #EggSMCLient will + * function normally. + * + * This must be called before the application's main loop begins. + **/ +void +egg_sm_client_set_mode (EggSMClientMode mode) +{ + global_client_mode = mode; +} + +/** + * egg_sm_client_get_mode: + * + * Gets the global #EggSMClientMode. See egg_sm_client_set_mode() + * for details. + * + * Return value: the global #EggSMClientMode + **/ +EggSMClientMode +egg_sm_client_get_mode (void) +{ + return global_client_mode; +} + +/** + * egg_sm_client_get: + * + * Returns the master #EggSMClient for the application. + * + * On platforms that support saved sessions (ie, POSIX/X11), the + * application will only request to be restarted by the session + * manager if you call egg_set_desktop_file() to set an application + * desktop file. In particular, if the desktop file contains the key + * "X + * + * Return value: the master #EggSMClient. + **/ +EggSMClient * +egg_sm_client_get (void) +{ + if (!global_client) + { + if (global_client_mode != EGG_SM_CLIENT_MODE_DISABLED && + !sm_client_disable) + { +#if defined (GDK_WINDOWING_WIN32) + global_client = egg_sm_client_win32_new (); +#elif defined (GDK_WINDOWING_QUARTZ) + global_client = egg_sm_client_osx_new (); +#else + /* If both D-Bus and XSMP are compiled in, try XSMP first + * (since it supports state saving) and fall back to D-Bus + * if XSMP isn't available. + */ +# ifdef EGG_SM_CLIENT_BACKEND_XSMP + global_client = egg_sm_client_xsmp_new (); +# endif +# ifdef EGG_SM_CLIENT_BACKEND_DBUS + if (!global_client) + global_client = egg_sm_client_dbus_new (); +# endif +#endif + } + + /* Fallback: create a dummy client, so that callers don't have + * to worry about a %NULL return value. + */ + if (!global_client) + global_client = g_object_new (EGG_TYPE_SM_CLIENT, NULL); + } + + return global_client; +} + +/** + * egg_sm_client_is_resumed: + * @client: the client + * + * Checks whether or not the current session has been resumed from + * a previous saved session. If so, the application should call + * egg_sm_client_get_state_file() and restore its state from the + * returned #GKeyFile. + * + * Return value: %TRUE if the session has been resumed + **/ +gboolean +egg_sm_client_is_resumed (EggSMClient *client) +{ + g_return_val_if_fail (client == global_client, FALSE); + + return sm_client_state_file != NULL; +} + +/** + * egg_sm_client_get_state_file: + * @client: the client + * + * If the application was resumed by the session manager, this will + * return the #GKeyFile containing its state from the previous + * session. + * + * Note that other libraries and #EggSMClient itself may also store + * state in the key file, so if you call egg_sm_client_get_groups(), + * on it, the return value will likely include groups that you did not + * put there yourself. (It is also not guaranteed that the first + * group created by the application will still be the "start group" + * when it is resumed.) + * + * Return value: the #GKeyFile containing the application's earlier + * state, or %NULL on error. You should not free this key file; it + * is owned by @client. + **/ +GKeyFile * +egg_sm_client_get_state_file (EggSMClient *client) +{ + EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client); + char *state_file_path; + GError *err = NULL; + + g_return_val_if_fail (client == global_client, NULL); + + if (!sm_client_state_file) + return NULL; + if (priv->state_file) + return priv->state_file; + + if (!strncmp (sm_client_state_file, "file://", 7)) + state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL); + else + state_file_path = g_strdup (sm_client_state_file); + + priv->state_file = g_key_file_new (); + if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err)) + { + g_warning ("Could not load SM state file '%s': %s", + sm_client_state_file, err->message); + g_clear_error (&err); + g_key_file_free (priv->state_file); + priv->state_file = NULL; + } + + g_free (state_file_path); + return priv->state_file; +} + +/** + * egg_sm_client_set_restart_command: + * @client: the client + * @argc: the length of @argv + * @argv: argument vector + * + * Sets the command used to restart @client if it does not have a + * .desktop file that can be used to find its restart command. + * + * This can also be used when handling the ::save_state signal, to + * save the current state via an updated command line. (Eg, providing + * a list of filenames to open when the application is resumed.) + **/ +void +egg_sm_client_set_restart_command (EggSMClient *client, + int argc, + const char **argv) +{ + g_return_if_fail (EGG_IS_SM_CLIENT (client)); + + if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command) + EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv); +} + +/** + * egg_sm_client_will_quit: + * @client: the client + * @will_quit: whether or not the application is willing to quit + * + * This MUST be called in response to the ::quit_requested signal, to + * indicate whether or not the application is willing to quit. The + * application may call it either directly from the signal handler, or + * at some later point (eg, after asynchronously interacting with the + * user). + * + * If the application does not connect to ::quit_requested, + * #EggSMClient will call this method on its behalf (passing %TRUE + * for @will_quit). + * + * After calling this method, the application should wait to receive + * either ::quit_cancelled or ::quit. + **/ +void +egg_sm_client_will_quit (EggSMClient *client, + gboolean will_quit) +{ + g_return_if_fail (EGG_IS_SM_CLIENT (client)); + + if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit) + EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit); +} + +/** + * egg_sm_client_end_session: + * @style: a hint at how to end the session + * @request_confirmation: whether or not the user should get a chance + * to confirm the action + * + * Requests that the session manager end the current session. @style + * indicates how the session should be ended, and + * @request_confirmation indicates whether or not the user should be + * given a chance to confirm the logout/reboot/shutdown. Both of these + * flags are merely hints though; the session manager may choose to + * ignore them. + * + * Return value: %TRUE if the request was sent; %FALSE if it could not + * be (eg, because it could not connect to the session manager). + **/ +gboolean +egg_sm_client_end_session (EggSMClientEndStyle style, + gboolean request_confirmation) +{ + EggSMClient *client = egg_sm_client_get (); + + g_return_val_if_fail (EGG_IS_SM_CLIENT (client), FALSE); + + if (EGG_SM_CLIENT_GET_CLASS (client)->end_session) + { + return EGG_SM_CLIENT_GET_CLASS (client)->end_session (client, style, + request_confirmation); + } + else + return FALSE; +} + +/* Signal-emitting callbacks from platform-specific code */ + +GKeyFile * +egg_sm_client_save_state (EggSMClient *client) +{ + GKeyFile *state_file; + char *group; + + g_return_val_if_fail (client == global_client, NULL); + + state_file = g_key_file_new (); + + g_debug ("Emitting save_state"); + g_signal_emit (client, signals[SAVE_STATE], 0, state_file); + g_debug ("Done emitting save_state"); + + group = g_key_file_get_start_group (state_file); + if (group) + { + g_free (group); + return state_file; + } + else + { + g_key_file_free (state_file); + return NULL; + } +} + +void +egg_sm_client_quit_requested (EggSMClient *client) +{ + g_return_if_fail (client == global_client); + + if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE)) + { + g_debug ("Not emitting quit_requested because no one is listening"); + egg_sm_client_will_quit (client, TRUE); + return; + } + + g_debug ("Emitting quit_requested"); + g_signal_emit (client, signals[QUIT_REQUESTED], 0); + g_debug ("Done emitting quit_requested"); +} + +void +egg_sm_client_quit_cancelled (EggSMClient *client) +{ + g_return_if_fail (client == global_client); + + g_debug ("Emitting quit_cancelled"); + g_signal_emit (client, signals[QUIT_CANCELLED], 0); + g_debug ("Done emitting quit_cancelled"); +} + +void +egg_sm_client_quit (EggSMClient *client) +{ + g_return_if_fail (client == global_client); + + g_debug ("Emitting quit"); + g_signal_emit (client, signals[QUIT], 0); + g_debug ("Done emitting quit"); + + /* FIXME: should we just call gtk_main_quit() here? */ +} + +static void +egg_sm_client_debug_handler (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + gpointer user_data) +{ + static int debug = -1; + + if (debug < 0) + debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL); + + if (debug) + g_log_default_handler (log_domain, log_level, message, NULL); +} diff --git a/cut-n-paste/smclient/eggsmclient.h b/cut-n-paste/smclient/eggsmclient.h new file mode 100644 index 00000000..65e258b3 --- /dev/null +++ b/cut-n-paste/smclient/eggsmclient.h @@ -0,0 +1,117 @@ +/* eggsmclient.h + * Copyright (C) 2007 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __EGG_SM_CLIENT_H__ +#define __EGG_SM_CLIENT_H__ + +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_SM_CLIENT (egg_sm_client_get_type ()) +#define EGG_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT, EggSMClient)) +#define EGG_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT, EggSMClientClass)) +#define EGG_IS_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT)) +#define EGG_IS_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT)) +#define EGG_SM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT, EggSMClientClass)) + +typedef struct _EggSMClient EggSMClient; +typedef struct _EggSMClientClass EggSMClientClass; +typedef struct _EggSMClientPrivate EggSMClientPrivate; + +typedef enum { + EGG_SM_CLIENT_END_SESSION_DEFAULT, + EGG_SM_CLIENT_LOGOUT, + EGG_SM_CLIENT_REBOOT, + EGG_SM_CLIENT_SHUTDOWN +} EggSMClientEndStyle; + +typedef enum { + EGG_SM_CLIENT_MODE_DISABLED, + EGG_SM_CLIENT_MODE_NO_RESTART, + EGG_SM_CLIENT_MODE_NORMAL +} EggSMClientMode; + +struct _EggSMClient +{ + GObject parent; + +}; + +struct _EggSMClientClass +{ + GObjectClass parent_class; + + /* signals */ + void (*save_state) (EggSMClient *client, + GKeyFile *state_file); + + void (*quit_requested) (EggSMClient *client); + void (*quit_cancelled) (EggSMClient *client); + void (*quit) (EggSMClient *client); + + /* virtual methods */ + void (*startup) (EggSMClient *client, + const char *client_id); + void (*set_restart_command) (EggSMClient *client, + int argc, + const char **argv); + void (*will_quit) (EggSMClient *client, + gboolean will_quit); + gboolean (*end_session) (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + + /* Padding for future expansion */ + void (*_egg_reserved1) (void); + void (*_egg_reserved2) (void); + void (*_egg_reserved3) (void); + void (*_egg_reserved4) (void); +}; + +GType egg_sm_client_get_type (void) G_GNUC_CONST; + +GOptionGroup *egg_sm_client_get_option_group (void); + +/* Initialization */ +void egg_sm_client_set_mode (EggSMClientMode mode); +EggSMClientMode egg_sm_client_get_mode (void); +EggSMClient *egg_sm_client_get (void); + +/* Resuming a saved session */ +gboolean egg_sm_client_is_resumed (EggSMClient *client); +GKeyFile *egg_sm_client_get_state_file (EggSMClient *client); + +/* Alternate means of saving state */ +void egg_sm_client_set_restart_command (EggSMClient *client, + int argc, + const char **argv); + +/* Handling "quit_requested" signal */ +void egg_sm_client_will_quit (EggSMClient *client, + gboolean will_quit); + +/* Initiate a logout/reboot/shutdown */ +gboolean egg_sm_client_end_session (EggSMClientEndStyle style, + gboolean request_confirmation); + +G_END_DECLS + + +#endif /* __EGG_SM_CLIENT_H__ */ diff --git a/cut-n-paste/synctex/Makefile.am b/cut-n-paste/synctex/Makefile.am new file mode 100644 index 00000000..881ef4ab --- /dev/null +++ b/cut-n-paste/synctex/Makefile.am @@ -0,0 +1,14 @@ +noinst_LTLIBRARIES = libsynctex.la + +libsynctex_la_SOURCES = \ + synctex_parser.c \ + synctex_parser.h \ + synctex_parser_utils.h \ + synctex_parser_utils.c + +libsynctex_la_CFLAGS = \ + $(SHELL_CORE_CFLAGS) \ + $(WARNING_CFLAGS) \ + $(DISABLE_DEPRECATED) + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/synctex/synctex_parser.c b/cut-n-paste/synctex/synctex_parser.c new file mode 100644 index 00000000..060f32d1 --- /dev/null +++ b/cut-n-paste/synctex/synctex_parser.c @@ -0,0 +1,4171 @@ +/* +Copyright (c) 2008, 2009; 2010 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Version: 1.12 +See synctex_parser_readme.txt for more details + +Latest Revision: Mon Jul 19 21:50:36 UTC 2010 + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Version 1 +Thu Jun 19 09:39:21 UTC 2008 + +*/ + +/* We assume that high level application like pdf viewers will want + * to embed this code as is. We assume that they also have locale.h and setlocale. + * For other tools such as TeXLive tools, you must define SYNCTEX_USE_LOCAL_HEADER, + * when building. You also have to create and customize synctex_parser_local.h to fit your system. + * In particular, the HAVE_LOCALE_H and HAVE_SETLOCALE macros should be properly defined. + * With this design, you should not need to edit this file. */ + +# if defined(SYNCTEX_USE_LOCAL_HEADER) +# include "synctex_parser_local.h" +# else +# define HAVE_LOCALE_H 1 +# define HAVE_SETLOCALE 1 +# if defined(_MSC_VER) +# define SYNCTEX_INLINE __inline +# else +# define SYNCTEX_INLINE inline +# endif +# endif + +#include +#include +#include +#include +#include + +#if defined(HAVE_LOCALE_H) +#include +#endif + +/* The data is organized in a graph with multiple entries. + * The root object is a scanner, it is created with the contents on a synctex file. + * Each leaf of the tree is a synctex_node_t object. + * There are 3 subtrees, two of them sharing the same leaves. + * The first tree is the list of input records, where input file names are associated with tags. + * The second tree is the box tree as given by TeX when shipping pages out. + * First level objects are sheets, containing boxes, glues, kerns... + * The third tree allows to browse leaves according to tag and line. + */ + +#include "synctex_parser.h" +#include + +/* These are the possible extensions of the synctex file */ +const char * synctex_suffix = ".synctex"; +const char * synctex_suffix_gz = ".gz"; + +static const char * synctex_io_modes[synctex_io_mode_append+2] = {"r","rb","a","ab"}; + +/* each synctex node has a class */ +typedef struct __synctex_class_t _synctex_class_t; +typedef _synctex_class_t * synctex_class_t; + + +/* synctex_node_t is a pointer to a node + * _synctex_node is the target of the synctex_node_t pointer + * It is a pseudo object oriented program. + * class is a pointer to the class object the node belongs to. + * implementation is meant to contain the private data of the node + * basically, there are 2 kinds of information: navigation information and + * synctex information. Both will depend on the type of the node, + * thus different nodes will have different private data. + * There is no inheritancy overhead. + */ +typedef union _synctex_info_t { + int INT; + char * PTR; +} synctex_info_t; + +struct _synctex_node { + synctex_class_t class; + synctex_info_t * implementation; +}; + +/* Each node of the tree, except the scanner itself belongs to a class. + * The class object is just a struct declaring the owning scanner + * This is a pointer to the scanner as root of the tree. + * The type is used to identify the kind of node. + * The class declares pointers to a creator and a destructor method. + * The log and display fields are used to log and display the node. + * display will also display the child, sibling and parent sibling. + * parent, child and sibling are used to navigate the tree, + * from TeX box hierarchy point of view. + * The friend field points to a method which allows to navigate from friend to friend. + * A friend is a node with very close tag and line numbers. + * Finally, the info field point to a method giving the private node info offset. + */ + +typedef synctex_node_t *(*_synctex_node_getter_t)(synctex_node_t); +typedef synctex_info_t *(*_synctex_info_getter_t)(synctex_node_t); + +struct __synctex_class_t { + synctex_scanner_t scanner; + int type; + synctex_node_t (*new)(synctex_scanner_t scanner); + void (*free)(synctex_node_t); + void (*log)(synctex_node_t); + void (*display)(synctex_node_t); + _synctex_node_getter_t parent; + _synctex_node_getter_t child; + _synctex_node_getter_t sibling; + _synctex_node_getter_t friend; + _synctex_node_getter_t next_box; + _synctex_info_getter_t info; +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Abstract OBJECTS and METHODS +# endif + +/* These macros are shortcuts + * This macro checks if a message can be sent. + */ +# define SYNCTEX_CAN_PERFORM(NODE,SELECTOR)\ + (NULL!=((((NODE)->class))->SELECTOR)) + +/* This macro is some kind of objc_msg_send. + * It takes care of sending the proper message if possible. + */ +# define SYNCTEX_MSG_SEND(NODE,SELECTOR) if(NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR)) {\ + (*((((NODE)->class))->SELECTOR))(NODE);\ + } + +/* read only safe getter + */ +# define SYNCTEX_GET(NODE,SELECTOR)((NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR))?SYNCTEX_GETTER(NODE,SELECTOR)[0]:(NULL)) + +/* read/write getter + */ +# define SYNCTEX_GETTER(NODE,SELECTOR)\ + ((synctex_node_t *)((*((((NODE)->class))->SELECTOR))(NODE))) + +# define SYNCTEX_FREE(NODE) SYNCTEX_MSG_SEND(NODE,free); + +/* Parent getter and setter + */ +# define SYNCTEX_PARENT(NODE) SYNCTEX_GET(NODE,parent) +# define SYNCTEX_SET_PARENT(NODE,NEW_PARENT) if(NODE && NEW_PARENT && SYNCTEX_CAN_PERFORM(NODE,parent)){\ + SYNCTEX_GETTER(NODE,parent)[0]=NEW_PARENT;\ + } + +/* Child getter and setter + */ +# define SYNCTEX_CHILD(NODE) SYNCTEX_GET(NODE,child) +# define SYNCTEX_SET_CHILD(NODE,NEW_CHILD) if(NODE && NEW_CHILD){\ + SYNCTEX_GETTER(NODE,child)[0]=NEW_CHILD;\ + SYNCTEX_GETTER(NEW_CHILD,parent)[0]=NODE;\ + } + +/* Sibling getter and setter + */ +# define SYNCTEX_SIBLING(NODE) SYNCTEX_GET(NODE,sibling) +# define SYNCTEX_SET_SIBLING(NODE,NEW_SIBLING) if(NODE && NEW_SIBLING) {\ + SYNCTEX_GETTER(NODE,sibling)[0]=NEW_SIBLING;\ + if(SYNCTEX_CAN_PERFORM(NEW_SIBLING,parent) && SYNCTEX_CAN_PERFORM(NODE,parent)) {\ + SYNCTEX_GETTER(NEW_SIBLING,parent)[0]=SYNCTEX_GETTER(NODE,parent)[0];\ + }\ + } +/* Friend getter and setter. A friend is a kern, math, glue or void box node which tag and line numbers are similar. + * This is a first filter on the nodes that avoids testing all of them. + * Friends are used mainly in forward synchronization aka from source to output. + */ +# define SYNCTEX_FRIEND(NODE) SYNCTEX_GET(NODE,friend) +# define SYNCTEX_SET_FRIEND(NODE,NEW_FRIEND) if(NODE && NEW_FRIEND){\ + SYNCTEX_GETTER(NODE,friend)[0]=NEW_FRIEND;\ + } + +/* Next box getter and setter. The box tree can be traversed from one horizontal box to the other. + * Navigation starts with the deeper boxes. + */ +# define SYNCTEX_NEXT_HORIZ_BOX(NODE) SYNCTEX_GET(NODE,next_box) +# define SYNCTEX_SET_NEXT_HORIZ_BOX(NODE,NEXT_BOX) if(NODE && NEXT_BOX){\ + SYNCTEX_GETTER(NODE,next_box)[0]=NEXT_BOX;\ + } + +void _synctex_free_node(synctex_node_t node); +void _synctex_free_leaf(synctex_node_t node); + +/* A node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for all nodes with children. + */ +void _synctex_free_node(synctex_node_t node) { + if(node) { + (*((node->class)->sibling))(node); + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + SYNCTEX_FREE(SYNCTEX_CHILD(node)); + free(node); + } + return; +} + +/* A node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for nodes with no child. + */ +void _synctex_free_leaf(synctex_node_t node) { + if(node) { + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + free(node); + } + return; +} +# ifdef __SYNCTEX_WORK__ +# include "/usr/include/zlib.h" +# else +# include +# endif + +/* The synctex scanner is the root object. + * Is is initialized with the contents of a text file or a gzipped file. + * The buffer_? are first used to parse the text. + */ +struct __synctex_scanner_t { + gzFile file; /* The (possibly compressed) file */ + char * buffer_cur; /* current location in the buffer */ + char * buffer_start; /* start of the buffer */ + char * buffer_end; /* end of the buffer */ + char * output_fmt; /* dvi or pdf, not yet used */ + char * output; /* the output name used to create the scanner */ + char * synctex; /* the .synctex or .synctex.gz name used to create the scanner */ + int version; /* 1, not yet used */ + struct { + unsigned has_parsed:1; /* Whether the scanner has parsed its underlying synctex file. */ + unsigned reserved:sizeof(unsigned)-1; /* alignment */ + } flags; + int pre_magnification; /* magnification from the synctex preamble */ + int pre_unit; /* unit from the synctex preamble */ + int pre_x_offset; /* X offste from the synctex preamble */ + int pre_y_offset; /* Y offset from the synctex preamble */ + int count; /* Number of records, from the synctex postamble */ + float unit; /* real unit, from synctex preamble or post scriptum */ + float x_offset; /* X offset, from synctex preamble or post scriptum */ + float y_offset; /* Y Offset, from synctex preamble or post scriptum */ + synctex_node_t sheet; /* The first sheet node, its siblings are the other sheet nodes */ + synctex_node_t input; /* The first input node, its siblings are the other input nodes */ + int number_of_lists; /* The number of friend lists */ + synctex_node_t * lists_of_friends;/* The friend lists */ + _synctex_class_t class[synctex_node_number_of_types]; /* The classes of the nodes of the scanner */ +}; + +/* SYNCTEX_CUR, SYNCTEX_START and SYNCTEX_END are convenient shortcuts + */ +# define SYNCTEX_CUR (scanner->buffer_cur) +# define SYNCTEX_START (scanner->buffer_start) +# define SYNCTEX_END (scanner->buffer_end) + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark OBJECTS, their creators and destructors. +# endif + +/* Here, we define the indices for the different informations. + * They are used to declare the size of the implementation. + * For example, if one object uses SYNCTEX_HORIZ_IDX is its size, + * then its info will contain a tag, line, column, horiz but no width nor height nor depth + */ + +/* The sheet is a first level node. + * It has no parent (the parent is the scanner itself) + * Its sibling points to another sheet. + * Its child points to its first child, in general a box. + * A sheet node contains only one synctex information: the page. + * This is the 1 based page index as given by TeX. + */ +/* The next macros are used to access the node info + * SYNCTEX_INFO(node) points to the first synctex integer or pointer data of node + * SYNCTEX_INFO(node)[index] is the information at index + * for example, the page of a sheet is stored in SYNCTEX_INFO(sheet)[SYNCTEX_PAGE_IDX] + */ +# define SYNCTEX_INFO(NODE) ((*((((NODE)->class))->info))(NODE)) +# define SYNCTEX_PAGE_IDX 0 +# define SYNCTEX_PAGE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_PAGE_IDX].INT + +/* This macro defines implementation offsets + * It is only used for pointer values + */ +# define SYNCTEX_MAKE_GET(SYNCTEX_GETTER,OFFSET)\ +synctex_node_t * SYNCTEX_GETTER (synctex_node_t node);\ +synctex_node_t * SYNCTEX_GETTER (synctex_node_t node) {\ + return node?(synctex_node_t *)((&((node)->implementation))+OFFSET):NULL;\ +} +SYNCTEX_MAKE_GET(_synctex_implementation_0,0) +SYNCTEX_MAKE_GET(_synctex_implementation_1,1) +SYNCTEX_MAKE_GET(_synctex_implementation_2,2) +SYNCTEX_MAKE_GET(_synctex_implementation_3,3) +SYNCTEX_MAKE_GET(_synctex_implementation_4,4) +SYNCTEX_MAKE_GET(_synctex_implementation_5,5) + +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_PAGE_IDX+1];/* child, sibling, next box, + * SYNCTEX_PAGE_IDX */ +} synctex_sheet_t; + +synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner); +void _synctex_display_sheet(synctex_node_t sheet); +void _synctex_log_sheet(synctex_node_t sheet); + +static _synctex_class_t synctex_class_sheet = { + NULL, /* No scanner yet */ + synctex_node_type_sheet, /* Node type */ + &_synctex_new_sheet, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_sheet, /* log */ + &_synctex_display_sheet, /* display */ + NULL, /* No parent */ + &_synctex_implementation_0, /* child */ + &_synctex_implementation_1, /* sibling */ + NULL, /* No friend */ + &_synctex_implementation_2, /* Next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 /* info */ +}; + +/* sheet node creator */ +synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_sheet_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_sheet:(synctex_class_t)&synctex_class_sheet; + } + return node; +} + +/* A box node contains navigation and synctex information + * There are different kind of boxes. + * Only horizontal boxes are treated differently because of their visible size. + */ +# define SYNCTEX_TAG_IDX 0 +# define SYNCTEX_LINE_IDX (SYNCTEX_TAG_IDX+1) +# define SYNCTEX_COLUMN_IDX (SYNCTEX_LINE_IDX+1) +# define SYNCTEX_HORIZ_IDX (SYNCTEX_COLUMN_IDX+1) +# define SYNCTEX_VERT_IDX (SYNCTEX_HORIZ_IDX+1) +# define SYNCTEX_WIDTH_IDX (SYNCTEX_VERT_IDX+1) +# define SYNCTEX_HEIGHT_IDX (SYNCTEX_WIDTH_IDX+1) +# define SYNCTEX_DEPTH_IDX (SYNCTEX_HEIGHT_IDX+1) +/* the corresponding info accessors */ +# define SYNCTEX_TAG(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_TAG_IDX].INT +# define SYNCTEX_LINE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_LINE_IDX].INT +# define SYNCTEX_COLUMN(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_COLUMN_IDX].INT +# define SYNCTEX_HORIZ(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_IDX].INT +# define SYNCTEX_VERT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_IDX].INT +# define SYNCTEX_WIDTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_IDX].INT +# define SYNCTEX_HEIGHT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_IDX].INT +# define SYNCTEX_DEPTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_IDX].INT +# define SYNCTEX_ABS_WIDTH(NODE) ((SYNCTEX_WIDTH(NODE)>0?SYNCTEX_WIDTH(NODE):-SYNCTEX_WIDTH(NODE))) +# define SYNCTEX_ABS_HEIGHT(NODE) ((SYNCTEX_HEIGHT(NODE)>0?SYNCTEX_HEIGHT(NODE):-SYNCTEX_HEIGHT(NODE))) +# define SYNCTEX_ABS_DEPTH(NODE) ((SYNCTEX_DEPTH(NODE)>0?SYNCTEX_DEPTH(NODE):-SYNCTEX_DEPTH(NODE))) + +typedef struct { + synctex_class_t class; + synctex_info_t implementation[5+SYNCTEX_DEPTH_IDX+1]; /* parent,child,sibling,friend,next box, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH */ +} synctex_vert_box_node_t; + +synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner); +void _synctex_log_box(synctex_node_t sheet); +void _synctex_display_vbox(synctex_node_t node); + +/* These are static class objects, each scanner will make a copy of them and setup the scanner field. + */ +static _synctex_class_t synctex_class_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_vbox, /* Node type */ + &_synctex_new_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_box, /* log */ + &_synctex_display_vbox, /* display */ + &_synctex_implementation_0, /* parent */ + &_synctex_implementation_1, /* child */ + &_synctex_implementation_2, /* sibling */ + &_synctex_implementation_3, /* friend */ + &_synctex_implementation_4, /* next box */ + (_synctex_info_getter_t)&_synctex_implementation_5 +}; + +/* vertical box node creator */ +synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_vert_box_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_vbox:(synctex_class_t)&synctex_class_vbox; + } + return node; +} + +# define SYNCTEX_HORIZ_V_IDX (SYNCTEX_DEPTH_IDX+1) +# define SYNCTEX_VERT_V_IDX (SYNCTEX_HORIZ_V_IDX+1) +# define SYNCTEX_WIDTH_V_IDX (SYNCTEX_VERT_V_IDX+1) +# define SYNCTEX_HEIGHT_V_IDX (SYNCTEX_WIDTH_V_IDX+1) +# define SYNCTEX_DEPTH_V_IDX (SYNCTEX_HEIGHT_V_IDX+1) +/* the corresponding info accessors */ +# define SYNCTEX_HORIZ_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_V_IDX].INT +# define SYNCTEX_VERT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_V_IDX].INT +# define SYNCTEX_WIDTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_V_IDX].INT +# define SYNCTEX_HEIGHT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_V_IDX].INT +# define SYNCTEX_DEPTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_V_IDX].INT +# define SYNCTEX_ABS_WIDTH_V(NODE) ((SYNCTEX_WIDTH_V(NODE)>0?SYNCTEX_WIDTH_V(NODE):-SYNCTEX_WIDTH_V(NODE))) +# define SYNCTEX_ABS_HEIGHT_V(NODE) ((SYNCTEX_HEIGHT_V(NODE)>0?SYNCTEX_HEIGHT_V(NODE):-SYNCTEX_HEIGHT_V(NODE))) +# define SYNCTEX_ABS_DEPTH_V(NODE) ((SYNCTEX_DEPTH_V(NODE)>0?SYNCTEX_DEPTH_V(NODE):-SYNCTEX_DEPTH_V(NODE))) + +/* Horizontal boxes must contain visible size, because 0 width does not mean emptiness */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[5+SYNCTEX_DEPTH_V_IDX+1]; /*parent,child,sibling,friend,next box, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH, + * SYNCTEX_HORIZ_V,SYNCTEX_VERT_V,SYNCTEX_WIDTH_V,SYNCTEX_HEIGHT_V,SYNCTEX_DEPTH_V*/ +} synctex_horiz_box_node_t; + +synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner); +void _synctex_display_hbox(synctex_node_t node); +void _synctex_log_horiz_box(synctex_node_t sheet); + + +static _synctex_class_t synctex_class_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_hbox, /* Node type */ + &_synctex_new_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_horiz_box, /* log */ + &_synctex_display_hbox, /* display */ + &_synctex_implementation_0, /* parent */ + &_synctex_implementation_1, /* child */ + &_synctex_implementation_2, /* sibling */ + &_synctex_implementation_3, /* friend */ + &_synctex_implementation_4, /* next box */ + (_synctex_info_getter_t)&_synctex_implementation_5 +}; + +/* horizontal box node creator */ +synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_horiz_box_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_hbox:(synctex_class_t)&synctex_class_hbox; + } + return node; +} + +/* This void box node implementation is either horizontal or vertical + * It does not contain a child field. + */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_DEPTH_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH*/ +} synctex_void_box_node_t; + +synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner); +void _synctex_log_void_box(synctex_node_t sheet); +void _synctex_display_void_vbox(synctex_node_t node); + +static _synctex_class_t synctex_class_void_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_vbox,/* Node type */ + &_synctex_new_void_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_vbox,/* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +/* vertical void box node creator */ +synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_void_box_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_void_vbox:(synctex_class_t)&synctex_class_void_vbox; + } + return node; +} + +synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner); +void _synctex_display_void_hbox(synctex_node_t node); + +static _synctex_class_t synctex_class_void_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_hbox,/* Node type */ + &_synctex_new_void_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_hbox,/* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +/* horizontal void box node creator */ +synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_void_box_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_void_hbox:(synctex_class_t)&synctex_class_void_hbox; + } + return node; +} + +/* The medium nodes correspond to kern, glue, penalty and math nodes. */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_WIDTH_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH */ +} synctex_medium_node_t; + +#define SYNCTEX_IS_BOX(NODE)\ + ((NODE->class->type == synctex_node_type_vbox)\ + || (NODE->class->type == synctex_node_type_void_vbox)\ + || (NODE->class->type == synctex_node_type_hbox)\ + || (NODE->class->type == synctex_node_type_void_hbox)) + +#define SYNCTEX_HAS_CHILDREN(NODE) (NODE && SYNCTEX_CHILD(NODE)) + +void _synctex_log_medium_node(synctex_node_t node); + +/* math node creator */ +synctex_node_t _synctex_new_math(synctex_scanner_t scanner); +void _synctex_display_math(synctex_node_t node); + +static _synctex_class_t synctex_class_math = { + NULL, /* No scanner yet */ + synctex_node_type_math, /* Node type */ + &_synctex_new_math, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_math, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_math(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_math:(synctex_class_t)&synctex_class_math; + } + return node; +} + +/* kern node creator */ +synctex_node_t _synctex_new_kern(synctex_scanner_t scanner); +void _synctex_display_kern(synctex_node_t node); + +static _synctex_class_t synctex_class_kern = { + NULL, /* No scanner yet */ + synctex_node_type_kern, /* Node type */ + &_synctex_new_kern, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_kern, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_kern(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_kern:(synctex_class_t)&synctex_class_kern; + } + return node; +} + +/* The small nodes correspond to glue and boundary nodes. */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_VERT_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT */ +} synctex_small_node_t; + +void _synctex_log_small_node(synctex_node_t node); +/* glue node creator */ +synctex_node_t _synctex_new_glue(synctex_scanner_t scanner); +void _synctex_display_glue(synctex_node_t node); + +static _synctex_class_t synctex_class_glue = { + NULL, /* No scanner yet */ + synctex_node_type_glue, /* Node type */ + &_synctex_new_glue, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_glue, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; +synctex_node_t _synctex_new_glue(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_glue:(synctex_class_t)&synctex_class_glue; + } + return node; +} + +/* boundary node creator */ +synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner); +void _synctex_display_boundary(synctex_node_t node); + +static _synctex_class_t synctex_class_boundary = { + NULL, /* No scanner yet */ + synctex_node_type_boundary, /* Node type */ + &_synctex_new_boundary, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_small_node, /* log */ + &_synctex_display_boundary, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_small_node_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_boundary:(synctex_class_t)&synctex_class_boundary; + } + return node; +} + +# define SYNCTEX_NAME_IDX (SYNCTEX_TAG_IDX+1) +# define SYNCTEX_NAME(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_NAME_IDX].PTR + +/* Input nodes only know about their sibling, which is another input node. + * The synctex information is the SYNCTEX_TAG and SYNCTEX_NAME*/ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[1+SYNCTEX_NAME_IDX+1]; /* sibling, + * SYNCTEX_TAG,SYNCTEX_NAME */ +} synctex_input_t; + +synctex_node_t _synctex_new_input(synctex_scanner_t scanner); +void _synctex_free_input(synctex_node_t node); +void _synctex_display_input(synctex_node_t node); +void _synctex_log_input(synctex_node_t sheet); + +static _synctex_class_t synctex_class_input = { + NULL, /* No scanner yet */ + synctex_node_type_input, /* Node type */ + &_synctex_new_input, /* creator */ + &_synctex_free_input, /* destructor */ + &_synctex_log_input, /* log */ + &_synctex_display_input, /* display */ + NULL, /* No parent */ + NULL, /* No child */ + &_synctex_implementation_0, /* sibling */ + NULL, /* No friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_1 +}; + +synctex_node_t _synctex_new_input(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_input_t)); + if(node) { + node->class = scanner?scanner->class+synctex_node_type_input:(synctex_class_t)&synctex_class_input; + } + return node; +} +void _synctex_free_input(synctex_node_t node){ + if(node) { + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + free(SYNCTEX_NAME(node)); + free(node); + } +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Navigation +# endif +synctex_node_t synctex_node_parent(synctex_node_t node) +{ + return SYNCTEX_PARENT(node); +} +synctex_node_t synctex_node_sheet(synctex_node_t node) +{ + while(node && node->class->type != synctex_node_type_sheet) { + node = SYNCTEX_PARENT(node); + } + /* exit the while loop either when node is NULL or node is a sheet */ + return node; +} +synctex_node_t synctex_node_child(synctex_node_t node) +{ + return SYNCTEX_CHILD(node); +} +synctex_node_t synctex_node_sibling(synctex_node_t node) +{ + return SYNCTEX_SIBLING(node); +} +synctex_node_t synctex_node_next(synctex_node_t node) { + if(SYNCTEX_CHILD(node)) { + return SYNCTEX_CHILD(node); + } +sibling: + if(SYNCTEX_SIBLING(node)) { + return SYNCTEX_SIBLING(node); + } + if((node = SYNCTEX_PARENT(node))) { + if(node->class->type == synctex_node_type_sheet) {/* EXC_BAD_ACCESS? */ + return NULL; + } + goto sibling; + } + return NULL; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark CLASS +# endif + +/* Public node accessor: the type */ +synctex_node_type_t synctex_node_type(synctex_node_t node) { + if(node) { + return (((node)->class))->type; + } + return synctex_node_type_error; +} + +/* Public node accessor: the human readable type */ +const char * synctex_node_isa(synctex_node_t node) { +static const char * isa[synctex_node_number_of_types] = + {"Not a node","input","sheet","vbox","void vbox","hbox","void hbox","kern","glue","math","boundary"}; + return isa[synctex_node_type(node)]; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SYNCTEX_LOG +# endif + +# define SYNCTEX_LOG(NODE) SYNCTEX_MSG_SEND(NODE,log) + +/* Public node logger */ +void synctex_node_log(synctex_node_t node) { + SYNCTEX_LOG(node); +} + +# define SYNCTEX_DISPLAY(NODE) SYNCTEX_MSG_SEND(NODE,display) + +void synctex_node_display(synctex_node_t node) { + SYNCTEX_DISPLAY(node); +} + +void _synctex_display_input(synctex_node_t node) { + printf("....Input:%i:%s\n", + SYNCTEX_TAG(node), + SYNCTEX_NAME(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_log_sheet(synctex_node_t sheet) { + if(sheet) { + printf("%s:%i\n",synctex_node_isa(sheet),SYNCTEX_PAGE(sheet)); + printf("SELF:%p",(void *)sheet); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(sheet)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(sheet)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(sheet)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(sheet)); + } +} + +void _synctex_log_small_node(synctex_node_t node) { + printf("%s:%i,%i:%i,%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + printf("SELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_medium_node(synctex_node_t node) { + printf("%s:%i,%i:%i,%i:%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node)); + printf("SELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_void_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_horiz_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("/%i",SYNCTEX_HORIZ_V(node)); + printf(",%i",SYNCTEX_VERT_V(node)); + printf(":%i",SYNCTEX_WIDTH_V(node)); + printf(",%i",SYNCTEX_HEIGHT_V(node)); + printf(",%i",SYNCTEX_DEPTH_V(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_input(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%s",SYNCTEX_NAME(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); +} + +void _synctex_display_sheet(synctex_node_t sheet) { + if(sheet) { + printf("....{%i\n",SYNCTEX_PAGE(sheet)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(sheet)); + printf("....}\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(sheet)); + } +} + +void _synctex_display_vbox(synctex_node_t node) { + printf("....[%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(node)); + printf("....]\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_hbox(synctex_node_t node) { + printf("....(%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(node)); + printf("....)\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_void_vbox(synctex_node_t node) { + printf("....v%i,%i;%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_void_hbox(synctex_node_t node) { + printf("....h%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_glue(synctex_node_t node) { + printf("....glue:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_math(synctex_node_t node) { + printf("....math:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_kern(synctex_node_t node) { + printf("....kern:%i,%i:%i,%i:%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_boundary(synctex_node_t node) { + printf("....boundary:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SCANNER +# endif + +/* Here are gathered all the possible status that the next scanning functions will return. + * All these functions return a status, and pass their result through pointers. + * Negative values correspond to errors. + * The management of the buffer is causing some significant overhead. + * Every function that may access the buffer returns a status related to the buffer and file state. + * status >= SYNCTEX_STATUS_OK means the function worked as expected + * status < SYNCTEX_STATUS_OK means the function did not work as expected + * status == SYNCTEX_STATUS_NOT_OK means the function did not work as expected but there is still some material to parse. + * status == SYNCTEX_STATUS_EOF means the function did not work as expected and there is no more material. + * statusfile) + +/* Actually, the minimum buffer size is driven by integer and float parsing. + * ±0.123456789e123 + */ +# define SYNCTEX_BUFFER_MIN_SIZE 16 +# define SYNCTEX_BUFFER_SIZE 32768 + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Prototypes +# endif +void _synctex_log_void_box(synctex_node_t node); +void _synctex_log_box(synctex_node_t node); +void _synctex_log_horiz_box(synctex_node_t node); +void _synctex_log_input(synctex_node_t node); +synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr); +synctex_status_t _synctex_next_line(synctex_scanner_t scanner); +synctex_status_t _synctex_match_string(synctex_scanner_t scanner, const char * the_string); +synctex_status_t _synctex_decode_int(synctex_scanner_t scanner, int* value_ref); +synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref); +synctex_status_t _synctex_scan_input(synctex_scanner_t scanner); +synctex_status_t _synctex_scan_preamble(synctex_scanner_t scanner); +synctex_status_t _synctex_scan_float_and_dimension(synctex_scanner_t scanner, float * value_ref); +synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner); +int _synctex_scan_postamble(synctex_scanner_t scanner); +synctex_status_t _synctex_setup_visible_box(synctex_node_t box); +synctex_status_t _synctex_horiz_box_setup_visible(synctex_node_t node,int h, int v); +synctex_status_t _synctex_scan_sheet(synctex_scanner_t scanner, synctex_node_t parent); +synctex_status_t _synctex_scan_content(synctex_scanner_t scanner); +int synctex_scanner_pre_x_offset(synctex_scanner_t scanner); +int synctex_scanner_pre_y_offset(synctex_scanner_t scanner); +const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner); +int _synctex_node_is_box(synctex_node_t node); +int _synctex_bail(void); + +/* Try to ensure that the buffer contains at least size bytes. + * Passing a huge size argument means the whole buffer length. + * Passing a null size argument means return the available buffer length, without reading the file. + * In that case, the return status is always SYNCTEX_STATUS_OK unless the given scanner is NULL, + * in which case, SYNCTEX_STATUS_BAD_ARGUMENT is returned. + * The value returned in size_ptr is the number of bytes now available in the buffer. + * This is a nonnegative integer, it may take the value 0. + * It is the responsibility of the caller to test whether this size is conforming to its needs. + * Negative values may return in case of error, actually + * when there was an error reading the synctex file. */ +synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr) { + size_t available = 0; + if(NULL == scanner || NULL == size_ptr) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +# define size (* size_ptr) + if(size>SYNCTEX_BUFFER_SIZE){ + size = SYNCTEX_BUFFER_SIZE; + } + available = SYNCTEX_END - SYNCTEX_CUR; /* available is the number of unparsed chars in the buffer */ + if(size<=available) { + /* There are already sufficiently many characters in the buffer */ + size = available; + return SYNCTEX_STATUS_OK; + } + if(SYNCTEX_FILE) { + /* Copy the remaining part of the buffer to the beginning, + * then read the next part of the file */ + int already_read = 0; + if(available) { + memmove(SYNCTEX_START, SYNCTEX_CUR, available); + } + SYNCTEX_CUR = SYNCTEX_START + available; /* the next character after the move, will change. */ + /* Fill the buffer up to its end */ + already_read = gzread(SYNCTEX_FILE,(void *)SYNCTEX_CUR,SYNCTEX_BUFFER_SIZE - available); + if(already_read>0) { + /* We assume that 0already_read) { + /* There is an error in zlib */ + int errnum = 0; + const char * error_string = gzerror(SYNCTEX_FILE, &errnum); + if(Z_ERRNO == errnum) { + /* There is an error in zlib caused by the file system */ + _synctex_error("gzread error from the file system (%i)",errno); + } else { + _synctex_error("gzread error (%i:%i,%s)",already_read,errnum,error_string); + } + return SYNCTEX_STATUS_ERROR; + } else { + /* Nothing was read, we are at the end of the file. */ + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + SYNCTEX_END = SYNCTEX_CUR; + SYNCTEX_CUR = SYNCTEX_START; + * SYNCTEX_END = '\0';/* Terminate the string properly.*/ + size = SYNCTEX_END - SYNCTEX_CUR; + return SYNCTEX_STATUS_EOF; /* there might be a bit of text left */ + } + /* At this point, the function has already returned from above */ + } + /* We cannot enlarge the buffer because the end of the file was reached. */ + size = available; + return SYNCTEX_STATUS_EOF; +# undef size +} + +/* Used when parsing the synctex file. + * Advance to the next character starting a line. + * Actually, only '\n' is recognized as end of line marker. + * On normal completion, the returned value is the number of unparsed characters available in the buffer. + * In general, it is a positive value, 0 meaning that the end of file was reached. + * -1 is returned in case of error, actually because there was an error while feeding the buffer. + * When the function returns with no error, SYNCTEX_CUR points to the first character of the next line, if any. + * J. Laurens: Sat May 10 07:52:31 UTC 2008 + */ +synctex_status_t _synctex_next_line(synctex_scanner_t scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + size_t available = 0; + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +infinite_loop: + while(SYNCTEX_CUR=remaining_len) { + /* The buffer is sufficiently big to hold the expected number of characters. */ + if(strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) { + return SYNCTEX_STATUS_NOT_OK; + } +return_OK: + /* Advance SYNCTEX_CUR to the next character after the_string. */ + SYNCTEX_CUR += remaining_len; + return SYNCTEX_STATUS_OK; + } else if(strncmp((char *)SYNCTEX_CUR,the_string,available)) { + /* No need to goo further, this is not the expected string in the buffer. */ + return SYNCTEX_STATUS_NOT_OK; + } else if(SYNCTEX_FILE) { + /* The buffer was too small to contain remaining_len characters. + * We have to cut the string into pieces. */ + z_off_t offset = 0L; + /* the first part of the string is found, advance the_string to the next untested character. */ + the_string += available; + /* update the remaining length and the parsed length. */ + remaining_len -= available; + tested_len += available; + SYNCTEX_CUR += available; /* We validate the tested characters. */ + if(0 == remaining_len) { + /* Nothing left to test, we have found the given string, we return the length. */ + return tested_len; + } + /* We also have to record the current state of the file cursor because + * if the_string does not match, all this should be a totally blank operation, + * for which the file and buffer states should not be modified at all. + * In fact, the states of the buffer before and after this function are in general different + * but they are totally equivalent as long as the values of the buffer before SYNCTEX_CUR + * can be safely discarded. */ + offset = gztell(SYNCTEX_FILE); + /* offset now corresponds to the first character of the file that was not buffered. */ + available = SYNCTEX_CUR - SYNCTEX_START; /* available can be used as temporary placeholder. */ + /* available now corresponds to the number of chars that where already buffered and + * that match the head of the_string. If in fine the_string does not match, all these chars must be recovered + * because the buffer contents is completely replaced by _synctex_buffer_get_available_size. + * They were buffered from offset-len location in the file. */ + offset -= available; +more_characters: + /* There is still some work to be done, so read another bunch of file. + * This is the second call to _synctex_buffer_get_available_size, + * which means that the actual contents of the buffer will be discarded. + * We will definitely have to recover the previous state in case we do not find the expected string. */ + available = remaining_len; + status = _synctex_buffer_get_available_size(scanner,&available); + if(statusptr) { + SYNCTEX_CUR = end; + if(value_ref) { + * value_ref = result; + } + return SYNCTEX_STATUS_OK;/* Successfully scanned an int */ + } + return SYNCTEX_STATUS_NOT_OK;/* Could not scan an int */ +} + +/* The purpose of this function is to read a string. + * A string is an array of characters from the current parser location + * and before the next '\n' character. + * If a string was properly decoded, it is returned in value_ref and + * the cursor points to the new line marker. + * The returned string was alloced on the heap, the caller is the owner and + * is responsible to free it in due time. + * If no string is parsed, * value_ref is undefined. + * The maximum length of a string that a scanner can decode is platform dependent, namely UINT_MAX. + * If you just want to blindly parse the file up to the end of the current line, + * use _synctex_next_line instead. + * On return, the scanner cursor is unchanged if a string could not be scanned or + * points to the terminating '\n' character otherwise. As a consequence, + * _synctex_next_line is necessary after. + * If either scanner or value_ref is NULL, it is considered as an error and + * SYNCTEX_STATUS_BAD_ARGUMENT is returned. + */ +synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref) { + char * end = NULL; + size_t current_size = 0; + size_t new_size = 0; + size_t len = 0;/* The number of bytes to copy */ + size_t available = 0; + synctex_status_t status = 0; + if(NULL == scanner || NULL == value_ref) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* The buffer must at least contain one character: the '\n' end of line marker */ + if(SYNCTEX_CUR>=SYNCTEX_END) { + available = 1; + status = _synctex_buffer_get_available_size(scanner,&available); + if(status < 0) { + return status; + } + if(0 == available) { + return SYNCTEX_STATUS_EOF; + } + } + /* Now we are sure that there is at least one available character, either because + * SYNCTEX_CUR was already < SYNCTEX_END, or because the buffer has been properly filled. */ + /* end will point to the next unparsed '\n' character in the file, when mapped to the buffer. */ + end = SYNCTEX_CUR; + * value_ref = NULL;/* Initialize, it will be realloc'ed */ + /* We scan all the characters up to the next '\n' */ +next_character: + if(endUINT_MAX-len-1) { + /* But we have reached the limit: we do not have current_size+len+1>UINT_MAX. + * We return the missing amount of memory. + * This will never occur in practice. */ + return UINT_MAX-len-1 - current_size; + } + new_size = current_size+len; + /* We have current_size+len+1<=UINT_MAX + * or equivalently new_sizeUINT_MAX-len-1) { + /* We have reached the limit. */ + _synctex_error("limit reached (missing %i).",current_size-(UINT_MAX-len-1)); + return SYNCTEX_STATUS_ERROR; + } + new_size = current_size+len; + if((* value_ref = realloc(* value_ref,new_size+1)) != NULL) { + if(memcpy((*value_ref)+current_size,SYNCTEX_CUR,len)) { + (* value_ref)[new_size]='\0'; /* Terminate the string */ + SYNCTEX_CUR = SYNCTEX_END;/* Advance the cursor to the end of the bufer */ + return SYNCTEX_STATUS_OK; + } + free(* value_ref); + * value_ref = NULL; + _synctex_error("could not copy memory (2)."); + return SYNCTEX_STATUS_ERROR; + } + /* Huge memory problem */ + _synctex_error("could not allocate memory (2)."); + return SYNCTEX_STATUS_ERROR; + } +} + +/* Used when parsing the synctex file. + * Read an Input record. + */ +synctex_status_t _synctex_scan_input(synctex_scanner_t scanner) { + synctex_status_t status = 0; + size_t available = 0; + synctex_node_t input = NULL; + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + status = _synctex_match_string(scanner,"Input:"); + if(statusinput); + scanner->input = input; + return _synctex_next_line(scanner);/* read the line termination character, if any */ + /* Now, set up the path */ +} + +typedef synctex_status_t (*synctex_decoder_t)(synctex_scanner_t,void *); + +synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder); + +/* Used when parsing the synctex file. + * Read one of the settings. + * On normal completion, returns SYNCTEX_STATUS_OK. + * On error, returns SYNCTEX_STATUS_ERROR. + * Both arguments must not be NULL. + * On return, the scanner points to the next character after the decoded object whatever it is. + * It is the responsibility of the caller to prepare the scanner for the next line. + */ +synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder) { + synctex_status_t status = 0; + if(NULL == scanner || NULL == name || NULL == value_ref || NULL == decoder) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +not_found: + status = _synctex_match_string(scanner,name); + if(statusversion),(synctex_decoder_t)&_synctex_decode_int); + if(statusoutput_fmt),(synctex_decoder_t)&_synctex_decode_string); + if(statuspre_magnification),(synctex_decoder_t)&_synctex_decode_int); + if(statuspre_unit),(synctex_decoder_t)&_synctex_decode_int); + if(statuspre_x_offset),(synctex_decoder_t)&_synctex_decode_int); + if(statuspre_y_offset),(synctex_decoder_t)&_synctex_decode_int); + if(status= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536; + } else if(status= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536/2.54f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"mm")) >= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536/25.4f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"pt")) >= SYNCTEX_STATUS_OK) { + f *= 65536.0f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"bp")) >= SYNCTEX_STATUS_OK) { + f *= 72.27f/72*65536.0f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"pc")) >= SYNCTEX_STATUS_OK) { + f *= 12.0*65536.0f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"sp")) >= SYNCTEX_STATUS_OK) { + f *= 1.0f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"dd")) >= SYNCTEX_STATUS_OK) { + f *= 1238.0f/1157*65536.0f; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"cc")) >= SYNCTEX_STATUS_OK) { + f *= 14856.0f/1157*65536; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"nd")) >= SYNCTEX_STATUS_OK) { + f *= 685.0f/642*65536; + } else if(status<0) { + goto report_unit_error; + } else if((status = _synctex_match_string(scanner,"nc")) >= SYNCTEX_STATUS_OK) { + f *= 1370.0f/107*65536; + } else if(status<0) { + goto report_unit_error; + } + *value_ref = f; + return SYNCTEX_STATUS_OK; +} + +/* parse the post scriptum + * SYNCTEX_STATUS_OK is returned on completion + * a negative error is returned otherwise */ +synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner) { + synctex_status_t status = 0; + char * endptr = NULL; +#ifdef HAVE_SETLOCALE + char * loc = setlocale(LC_NUMERIC, NULL); +#endif + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* Scan the file until a post scriptum line is found */ +post_scriptum_not_found: + status = _synctex_match_string(scanner,"Post scriptum:"); + if(statusunit = strtod(SYNCTEX_CUR,&endptr); +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, loc); +#endif + if(endptr == SYNCTEX_CUR) { + _synctex_error("bad magnification in the post scriptum, a float was expected."); + return SYNCTEX_STATUS_ERROR; + } + if(scanner->unit<=0) { + _synctex_error("bad magnification in the post scriptum, a positive float was expected."); + return SYNCTEX_STATUS_ERROR; + } + SYNCTEX_CUR = endptr; + goto next_line; + } + if(statusx_offset)); + if(statusy_offset)); + if(statuscount),(synctex_decoder_t)&_synctex_decode_int); + if(status < SYNCTEX_STATUS_EOF) { + return status; /* forward the error */ + } else if(status < SYNCTEX_STATUS_OK) { /* No Count record found */ + status = _synctex_next_line(scanner); /* Advance one more line */ + if(statusclass->type) { + case synctex_node_type_hbox: + if(SYNCTEX_INFO(box) != NULL) { + SYNCTEX_HORIZ_V(box) = SYNCTEX_HORIZ(box); + SYNCTEX_VERT_V(box) = SYNCTEX_VERT(box); + SYNCTEX_WIDTH_V(box) = SYNCTEX_WIDTH(box); + SYNCTEX_HEIGHT_V(box) = SYNCTEX_HEIGHT(box); + SYNCTEX_DEPTH_V(box) = SYNCTEX_DEPTH(box); + return SYNCTEX_STATUS_OK; + } + return SYNCTEX_STATUS_ERROR; + } + } + return SYNCTEX_STATUS_BAD_ARGUMENT; +} + +/* This method is sent to an horizontal box to setup the visible size + * Some box have 0 width but do contain text material. + * With this method, one can enlarge the box to contain the given point (h,v). + */ +synctex_status_t _synctex_horiz_box_setup_visible(synctex_node_t node,int h, int v) { +# ifdef __DARWIN_UNIX03 +# pragma unused(v) +# endif + int itsBtm, itsTop; + if(NULL == node || node->class->type != synctex_node_type_hbox) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + if(SYNCTEX_WIDTH_V(node)<0) { + itsBtm = SYNCTEX_HORIZ_V(node); + itsTop = SYNCTEX_HORIZ_V(node)-SYNCTEX_WIDTH_V(node); + if(hitsTop) { + SYNCTEX_WIDTH_V(node) = SYNCTEX_HORIZ_V(node) - h; + } + } else { + itsBtm = SYNCTEX_HORIZ_V(node); + itsTop = SYNCTEX_HORIZ_V(node)+SYNCTEX_WIDTH_V(node); + if(hitsTop) { + SYNCTEX_WIDTH_V(node) = h - SYNCTEX_HORIZ_V(node); + } + } + return SYNCTEX_STATUS_OK; +} + +/* Used when parsing the synctex file. + * The sheet argument is a newly created sheet node that will hold the contents. + * Something is returned in case of error. + */ +synctex_status_t _synctex_scan_sheet(synctex_scanner_t scanner, synctex_node_t sheet) { + synctex_node_t parent = sheet; + synctex_node_t child = NULL; + synctex_node_t sibling = NULL; + synctex_node_t box = sheet; + int friend_index = 0; + synctex_info_t * info = NULL; + synctex_status_t status = 0; + size_t available = 0; + if((NULL == scanner) || (NULL == sheet)) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* We MUST start with a box, so at this level, the unique possibility is '[', '(' or "}". */ +prepare_loop: + if(SYNCTEX_CURclass->type != synctex_node_type_sheet + || _synctex_next_line(scanner)0){ + _synctex_error("Uncomplete sheet(0)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } else { + goto prepare_loop; + } + } + _synctex_bail(); +/* The child loop means that we go do one level, when we just created a box node, + * the next node created is a child of this box. */ +child_loop: + if(SYNCTEX_CURclass->type == synctex_node_type_vbox) { + #define SYNCTEX_UPDATE_BOX_FRIEND(NODE)\ + friend_index = ((SYNCTEX_INFO(NODE))[SYNCTEX_TAG_IDX].INT+(SYNCTEX_INFO(NODE))[SYNCTEX_LINE_IDX].INT)%(scanner->number_of_lists);\ + SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\ + (scanner->lists_of_friends)[friend_index] = NODE; + if(NULL == SYNCTEX_CHILD(parent)) { + /* only void boxes are friends */ + SYNCTEX_UPDATE_BOX_FRIEND(parent); + } + child = parent; + parent = SYNCTEX_PARENT(child); + } else { + _synctex_error("Unexpected ']', ignored."); + } + if(_synctex_next_line(scanner)class->type == synctex_node_type_hbox) { + if(NULL == child) { + /* Only boxes with no children are friends, + * boxes with children are indirectly friends through one of their descendants. */ + SYNCTEX_UPDATE_BOX_FRIEND(parent); + } + /* setting the next horizontal box at the end ensures that a child is recorded before any of its ancestors. */ + SYNCTEX_SET_NEXT_HORIZ_BOX(box,parent); + box = parent; + child = parent; + parent = SYNCTEX_PARENT(child); + } else { + _synctex_error("Unexpected ')', ignored."); + } + if(_synctex_next_line(scanner)number_of_lists);\ + SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\ + (scanner->lists_of_friends)[friend_index] = NODE; + SYNCTEX_UPDATE_FRIEND(child); + goto sibling_loop; + } else { + _synctex_error("Can't create vbox record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if(*SYNCTEX_CUR == 'h') { + ++SYNCTEX_CUR; + if(NULL != (child = _synctex_new_void_hbox(scanner)) + && NULL != (info = SYNCTEX_INFO(child))) { + if(SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_next_line(scanner)0){ + _synctex_error("Uncomplete sheet(0)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } else { + goto child_loop; + } + } + _synctex_bail(); +/* The vertical loop means that we are on the same level, for example when we just ended a box. + * If a node is created now, it will be a sibling of the current node, sharing the same parent. */ +sibling_loop: + if(SYNCTEX_CUR0){ + goto sibling_loop; + } else { + _synctex_error("Uncomplete sheet(2)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } +# undef SYNCTEX_DECODE_FAILED +} + +/* Used when parsing the synctex file + */ +synctex_status_t _synctex_scan_content(synctex_scanner_t scanner) { + synctex_node_t sheet = NULL; + synctex_status_t status = 0; + if(NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* set up the lists of friends */ + if(NULL == scanner->lists_of_friends) { + scanner->number_of_lists = 1024; + scanner->lists_of_friends = (synctex_node_t *)_synctex_malloc(scanner->number_of_lists*sizeof(synctex_node_t)); + if(NULL == scanner->lists_of_friends) { + _synctex_error("malloc:2"); + return SYNCTEX_STATUS_ERROR; + } + } + /* Find where this section starts */ +content_not_found: + status = _synctex_match_string(scanner,"Content:"); + if(statussheet); + scanner->sheet = sheet; + sheet = NULL; + /* Now read the list of Inputs between 2 sheets. */ + do { + status = _synctex_scan_input(scanner); + if(status= SYNCTEX_STATUS_OK); + goto next_sheet; +} + +int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef); + +/* Where the synctex scanner is created. */ +synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse) { + gzFile file = NULL; + char * synctex = NULL; + synctex_scanner_t scanner = NULL; + synctex_io_mode_t io_mode = synctex_io_mode_read; + /* Here we assume that int are smaller than void * */ + if(sizeof(int)>sizeof(void*)) { + _synctex_error("INTERNAL INCONSISTENCY: int's are unexpectedly bigger than pointers, bailing out."); + return NULL; + } + /* We ensure that SYNCTEX_BUFFER_SIZE < UINT_MAX, I don't know if it makes sense... */ + if(SYNCTEX_BUFFER_SIZE >= UINT_MAX) { + _synctex_error("SyncTeX BUG: Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (1)"); + return NULL; + } + /* for integers: */ + if(SYNCTEX_BUFFER_SIZE < SYNCTEX_BUFFER_MIN_SIZE) { + _synctex_error("SyncTeX BUG: Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (2)"); + return NULL; + } + /* now open the synctex file */ + if(_synctex_open(output,build_directory,&synctex,&file,synctex_NO,&io_mode) || !file) { + if(_synctex_open(output,build_directory,&synctex,&file,synctex_YES,&io_mode) || !file) { + return NULL; + } + } + scanner = (synctex_scanner_t)_synctex_malloc(sizeof(_synctex_scanner_t)); + if(NULL == scanner) { + _synctex_error("SyncTeX: malloc problem"); + free(synctex); + gzclose(file); + return NULL; + } + /* make a private copy of output for the scanner */ + if(NULL == (scanner->output = (char *)malloc(strlen(output)+1))){ + _synctex_error("! synctex_scanner_new_with_output_file: Memory problem (2), scanner's output is not reliable."); + } else if(scanner->output != strcpy(scanner->output,output)) { + _synctex_error("! synctex_scanner_new_with_output_file: Copy problem, scanner's output is not reliable."); + } + scanner->synctex = synctex;/* Now the scanner owns synctex */ + SYNCTEX_FILE = file; + return parse? synctex_scanner_parse(scanner):scanner; +} + +int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef); + +/* This functions opens the file at the "output" given location. + * It manages the problem of quoted filenames that appear with pdftex an filenames containing the space character. + * In TeXLive 2008, the synctex file created with pdftex did contain unexpected quotes. + * This function will remove them if possible. + * 0 on success, non 0 on error. */ +int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef) { +# define synctex_name (*synctex_name_ref) +# define the_file (*file_ref) + if(synctex_name_ref && file_ref) { + char * quoteless = NULL; + synctex_io_mode_t io_mode = *io_modeRef; + const char * mode = synctex_io_modes[io_mode]; + size_t size = 0; + /* now create the synctex file name */ + size = strlen(output)+strlen(synctex_suffix)+strlen(synctex_suffix_gz)+1; + synctex_name = (char *)malloc(size); + if(NULL == synctex_name) { + _synctex_error("! __synctex_open: Memory problem (1)\n"); + return 1; + } + /* we have reserved for synctex enough memory to copy output, both suffices and 2 quotes, + * including the terminating character. size is free now. */ + if(synctex_name != strcpy(synctex_name,output)) { + _synctex_error("! __synctex_open: Copy problem\n"); +return_on_error: + free(synctex_name); + synctex_name = NULL;/* Don't forget to reinitialize. */ + the_file = NULL; /* Here as well */ + free(quoteless); + return 2; + } + /* remove the last path extension if any */ + _synctex_strip_last_path_extension(synctex_name); + if(!strlen(synctex_name)) { + goto return_on_error; + } + /* now insert quotes. */ + if(add_quotes) { + char * quoted = NULL; + if(_synctex_copy_with_quoting_last_path_component(synctex_name,"ed,size) || (NULL == quoted)) { + /* There was an error or quoting does not make sense: */ + goto return_on_error; + } + quoteless = synctex_name; + synctex_name = quoted; + } + /* Now add the first path extension. */ + if(synctex_name != strcat(synctex_name,synctex_suffix)){ + _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix); + goto return_on_error; + } + /* To quoteless as well. */ + if(quoteless && (quoteless != strcat(quoteless,synctex_suffix))){ + free(quoteless); + quoteless = NULL; + } + if(NULL == (the_file = gzopen(synctex_name,mode))) { + /* Could not open this file */ + if(errno != ENOENT) { + /* The file does exist, this is a lower lever error, I can't do anything. */ + _synctex_error("SyncTeX: could not open %s, error %i\n",synctex_name,errno); + goto return_on_error; + } + /* Try the compressed version */ + if(synctex_name != strcat(synctex_name,synctex_suffix_gz)){ + _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix_gz); + goto return_on_error; + } + ++io_mode; + mode = synctex_io_modes[io_mode]; /* the file is a compressed and is a binary file, this caused errors on Windows */ + /* To quoteless as well. */ + if(quoteless && (quoteless != strcat(quoteless,synctex_suffix_gz))){ + free(quoteless); + quoteless = NULL; + } + if(NULL == (the_file = gzopen(synctex_name,mode))) { + /* Could not open this file */ + if(errno != ENOENT) { + /* The file does exist, this is a lower lever error, I can't do anything. */ + _synctex_error("SyncTeX: could not open %s, error %i\n",synctex_name,errno); + } + goto return_on_error; + } + } + /* At this point, the file is properly open. + * If we are in the add_quotes mode, we change the file name by removing the quotes. */ + if(quoteless) { + gzclose(the_file); + if(rename(synctex_name,quoteless)) { + _synctex_error("SyncTeX: could not rename %s to %s, error %i\n",synctex_name,quoteless,errno); + /* Reopen the file. */ + if(NULL == (the_file = gzopen(synctex_name,mode))) { + /* Could not open this file */ + if(errno != ENOENT) { + /* The file does exist, this is a lower lever error, I can't do anything. */ + _synctex_error("SyncTeX: could not open again %s, error %i\n",synctex_name,errno); + } + goto return_on_error; + } + } else { + if(NULL == (the_file = gzopen(quoteless,mode))) { + /* Could not open this file */ + if(errno != ENOENT) { + /* The file does exist, this is a lower lever error, I can't do anything. */ + _synctex_error("SyncTeX: could not open renamed %s, error %i\n",quoteless,errno); + } + goto return_on_error; + } + /* The quote free file name should replace the old one:*/ + free(synctex_name); + synctex_name = quoteless; + quoteless = NULL; + } + } + /* We are returning properly so we can also return the proper io_mode */ + *io_modeRef = io_mode; + return 0; + } + return 3; /* Bad parameter. */ +# undef synctex_name +# undef the_file +} + +/* Opens the ouput file, taking into account the eventual build_directory. + * 0 on success, non 0 on error. */ +int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef) { +# define synctex_name (*synctex_name_ref) +# define the_file (*file_ref) + int result = __synctex_open(output,synctex_name_ref,file_ref,add_quotes,io_modeRef); + if((result || !*file_ref) && build_directory && strlen(build_directory)) { + char * build_output; + const char * lpc; + size_t size; + synctex_bool_t is_absolute; + build_output = NULL; + lpc = _synctex_last_path_component(output); + size = strlen(build_directory)+strlen(lpc)+2; + is_absolute = _synctex_path_is_absolute(build_directory); + if(!is_absolute) { + size += strlen(output); + } + if((build_output = (char *)malloc(size))) { + if(is_absolute) { + build_output[0] = '\0'; + } else { + if(build_output != strcpy(build_output,output)) { + return -4; + } + build_output[lpc-output]='\0'; + } + if(build_output == strcat(build_output,build_directory)) { + /* Append a path separator if necessary. */ + if(!SYNCTEX_IS_PATH_SEPARATOR(build_output[strlen(build_directory)-1])) { + if(build_output != strcat(build_output,"/")) { + return -2; + } + } + /* Append the last path component of the output. */ + if(build_output != strcat(build_output,lpc)) { + return -3; + } + return __synctex_open(build_output,synctex_name_ref,file_ref,add_quotes,io_modeRef); + } + } + return -1; + } + return result; +# undef synctex_name +# undef the_file +} + +/* The scanner destructor + */ +void synctex_scanner_free(synctex_scanner_t scanner) { + if(NULL == scanner) { + return; + } + if(SYNCTEX_FILE) { + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + } + SYNCTEX_FREE(scanner->sheet); + SYNCTEX_FREE(scanner->input); + free(SYNCTEX_START); + free(scanner->output_fmt); + free(scanner->output); + free(scanner->synctex); + free(scanner->lists_of_friends); + free(scanner); +} + +/* Where the synctex scanner parses the contents of the file. */ +synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner) { + synctex_status_t status = 0; + if(!scanner || scanner->flags.has_parsed) { + return scanner; + } + scanner->flags.has_parsed=1; + scanner->pre_magnification = 1000; + scanner->pre_unit = 8192; + scanner->pre_x_offset = scanner->pre_y_offset = 578; + /* initialize the offset with a fake unprobable value, + * If there is a post scriptum section, this value will be overriden by the real life value */ + scanner->x_offset = scanner->y_offset = 6.027e23f; + scanner->class[synctex_node_type_sheet] = synctex_class_sheet; + scanner->class[synctex_node_type_input] = synctex_class_input; + (scanner->class[synctex_node_type_input]).scanner = scanner; + (scanner->class[synctex_node_type_sheet]).scanner = scanner; + scanner->class[synctex_node_type_vbox] = synctex_class_vbox; + (scanner->class[synctex_node_type_vbox]).scanner = scanner; + scanner->class[synctex_node_type_void_vbox] = synctex_class_void_vbox; + (scanner->class[synctex_node_type_void_vbox]).scanner = scanner; + scanner->class[synctex_node_type_hbox] = synctex_class_hbox; + (scanner->class[synctex_node_type_hbox]).scanner = scanner; + scanner->class[synctex_node_type_void_hbox] = synctex_class_void_hbox; + (scanner->class[synctex_node_type_void_hbox]).scanner = scanner; + scanner->class[synctex_node_type_kern] = synctex_class_kern; + (scanner->class[synctex_node_type_kern]).scanner = scanner; + scanner->class[synctex_node_type_glue] = synctex_class_glue; + (scanner->class[synctex_node_type_glue]).scanner = scanner; + scanner->class[synctex_node_type_math] = synctex_class_math; + (scanner->class[synctex_node_type_math]).scanner = scanner; + scanner->class[synctex_node_type_boundary] = synctex_class_boundary; + (scanner->class[synctex_node_type_boundary]).scanner = scanner; + SYNCTEX_START = (char *)malloc(SYNCTEX_BUFFER_SIZE+1); /* one more character for null termination */ + if(NULL == SYNCTEX_START) { + _synctex_error("SyncTeX: malloc error"); + synctex_scanner_free(scanner); + return NULL; + } + SYNCTEX_END = SYNCTEX_START+SYNCTEX_BUFFER_SIZE; + /* SYNCTEX_END always points to a null terminating character. + * Maybe there is another null terminating character between SYNCTEX_CUR and SYNCTEX_END-1. + * At least, we are sure that SYNCTEX_CUR points to a string covering a valid part of the memory. */ + *SYNCTEX_END = '\0'; + SYNCTEX_CUR = SYNCTEX_END; + status = _synctex_scan_preamble(scanner); + if(statuspre_unit)/65536 pt = (scanner->pre_unit)/65781.76 bp + * 1 pt = 65536 sp */ + if(scanner->pre_unit<=0) { + scanner->pre_unit = 8192; + } + if(scanner->pre_magnification<=0) { + scanner->pre_magnification = 1000; + } + if(scanner->unit <= 0) { + /* no post magnification */ + scanner->unit = scanner->pre_unit / 65781.76;/* 65781.76 or 65536.0*/ + } else { + /* post magnification */ + scanner->unit *= scanner->pre_unit / 65781.76; + } + scanner->unit *= scanner->pre_magnification / 1000.0; + if(scanner->x_offset > 6e23) { + /* no post offset */ + scanner->x_offset = scanner->pre_x_offset * (scanner->pre_unit / 65781.76); + scanner->y_offset = scanner->pre_y_offset * (scanner->pre_unit / 65781.76); + } else { + /* post offset */ + scanner->x_offset /= 65781.76f; + scanner->y_offset /= 65781.76f; + } + return scanner; + #undef SYNCTEX_FILE +} + +/* Scanner accessors. + */ +int synctex_scanner_pre_x_offset(synctex_scanner_t scanner){ + return scanner?scanner->pre_x_offset:0; +} +int synctex_scanner_pre_y_offset(synctex_scanner_t scanner){ + return scanner?scanner->pre_y_offset:0; +} +int synctex_scanner_x_offset(synctex_scanner_t scanner){ + return scanner?scanner->x_offset:0; +} +int synctex_scanner_y_offset(synctex_scanner_t scanner){ + return scanner?scanner->y_offset:0; +} +float synctex_scanner_magnification(synctex_scanner_t scanner){ + return scanner?scanner->unit:1; +} +void synctex_scanner_display(synctex_scanner_t scanner) { + if(NULL == scanner) { + return; + } + printf("The scanner:\noutput:%s\noutput_fmt:%s\nversion:%i\n",scanner->output,scanner->output_fmt,scanner->version); + printf("pre_unit:%i\nx_offset:%i\ny_offset:%i\n",scanner->pre_unit,scanner->pre_x_offset,scanner->pre_y_offset); + printf("count:%i\npost_magnification:%f\npost_x_offset:%f\npost_y_offset:%f\n", + scanner->count,scanner->unit,scanner->x_offset,scanner->y_offset); + printf("The input:\n"); + SYNCTEX_DISPLAY(scanner->input); + if(scanner->count<1000) { + printf("The sheets:\n"); + SYNCTEX_DISPLAY(scanner->sheet); + printf("The friends:\n"); + if(scanner->lists_of_friends) { + int i = scanner->number_of_lists; + synctex_node_t node; + while(i--) { + printf("Friend index:%i\n",i); + node = (scanner->lists_of_friends)[i]; + while(node) { + printf("%s:%i,%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node) + ); + node = SYNCTEX_FRIEND(node); + } + } + } + } else { + printf("SyncTeX Warning: Too many objects\n"); + } +} +/* Public*/ +const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag) { + synctex_node_t input = NULL; + if(NULL == scanner) { + return NULL; + } + input = scanner->input; + do { + if(tag == SYNCTEX_TAG(input)) { + return (SYNCTEX_NAME(input)); + } + } while((input = SYNCTEX_SIBLING(input)) != NULL); + return NULL; +} + +int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name); +int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) { + synctex_node_t input = NULL; + if(NULL == scanner) { + return 0; + } + input = scanner->input; + do { + if(_synctex_is_equivalent_file_name(name,(SYNCTEX_NAME(input)))) { + return SYNCTEX_TAG(input); + } + } while((input = SYNCTEX_SIBLING(input)) != NULL); + return 0; +} + +int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) { + size_t char_index = strlen(name); + if((scanner = synctex_scanner_parse(scanner)) && (0 < char_index)) { + /* the name is not void */ + char_index -= 1; + if(!SYNCTEX_IS_PATH_SEPARATOR(name[char_index])) { + /* the last character of name is not a path separator */ + int result = _synctex_scanner_get_tag(scanner,name); + if(result) { + return result; + } else { + /* the given name was not the one known by TeX + * try a name relative to the enclosing directory of the scanner->output file */ + const char * relative = name; + const char * ptr = scanner->output; + while((strlen(relative) > 0) && (strlen(ptr) > 0) && (*relative == *ptr)) + { + relative += 1; + ptr += 1; + } + /* Find the last path separator before relative */ + while(relative > name) { + if(SYNCTEX_IS_PATH_SEPARATOR(*(relative-1))) { + break; + } + relative -= 1; + } + if((relative > name) && (result = _synctex_scanner_get_tag(scanner,relative))) { + return result; + } + if(SYNCTEX_IS_PATH_SEPARATOR(name[0])) { + /* No tag found for the given absolute name, + * Try each relative path starting from the shortest one */ + while(0input:NULL; +} +const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner) { + return NULL != scanner && scanner->output_fmt?scanner->output_fmt:""; +} +const char * synctex_scanner_get_output(synctex_scanner_t scanner) { + return NULL != scanner && scanner->output?scanner->output:""; +} +const char * synctex_scanner_get_synctex(synctex_scanner_t scanner) { + return NULL != scanner && scanner->synctex?scanner->synctex:""; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node attributes +# endif +int synctex_node_h(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_HORIZ(node); +} +int synctex_node_v(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_VERT(node); +} +int synctex_node_width(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_WIDTH(node); +} +int synctex_node_box_h(synctex_node_t node){ + if(!node) { + return 0; + } + if(SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_HORIZ(node); + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_v(synctex_node_t node){ + if(!node) { + return 0; + } + if(SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_VERT(node); + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_width(synctex_node_t node){ + if(!node) { + return 0; + } + if(SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_WIDTH(node); + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_height(synctex_node_t node){ + if(!node) { + return 0; + } + if(SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_HEIGHT(node); + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_depth(synctex_node_t node){ + if(!node) { + return 0; + } + if(SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_DEPTH(node); + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node visible attributes +# endif +float synctex_node_visible_h(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset; +} +float synctex_node_visible_v(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset; +} +float synctex_node_visible_width(synctex_node_t node){ + if(!node) { + return 0; + } + return SYNCTEX_WIDTH(node)*node->class->scanner->unit; +} +float synctex_node_box_visible_h(synctex_node_t node){ + if(!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset; + case synctex_node_type_hbox: +result: + return SYNCTEX_HORIZ_V(node)*node->class->scanner->unit+node->class->scanner->x_offset; + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_v(synctex_node_t node){ + if(!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset; + case synctex_node_type_hbox: +result: + return SYNCTEX_VERT_V(node)*node->class->scanner->unit+node->class->scanner->y_offset; + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_width(synctex_node_t node){ + if(!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_WIDTH(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_WIDTH_V(node)*node->class->scanner->unit; + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_height(synctex_node_t node){ + if(!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_HEIGHT(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_HEIGHT_V(node)*node->class->scanner->unit; + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_depth(synctex_node_t node){ + if(!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_DEPTH(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_DEPTH_V(node)*node->class->scanner->unit; + } + if((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Other public node attributes +# endif + +int synctex_node_page(synctex_node_t node){ + synctex_node_t parent = NULL; + if(!node) { + return -1; + } + parent = SYNCTEX_PARENT(node); + while(parent) { + node = parent; + parent = SYNCTEX_PARENT(node); + } + if(node->class->type == synctex_node_type_sheet) { + return SYNCTEX_PAGE(node); + } + return -1; +} +int synctex_node_tag(synctex_node_t node) { + return node?SYNCTEX_TAG(node):-1; +} +int synctex_node_line(synctex_node_t node) { + return node?SYNCTEX_LINE(node):-1; +} +int synctex_node_column(synctex_node_t node) { +# ifdef __DARWIN_UNIX03 +# pragma unused(node) +# endif + return -1; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Sheet +# endif + +synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page) { + if(scanner) { + synctex_node_t sheet = scanner->sheet; + while(sheet) { + if(page == SYNCTEX_PAGE(sheet)) { + return SYNCTEX_CHILD(sheet); + } + sheet = SYNCTEX_SIBLING(sheet); + } + } + return NULL; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Query +# endif + +int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column) { +# ifdef __DARWIN_UNIX03 +# pragma unused(column) +# endif + int tag = synctex_scanner_get_tag(scanner,name); + size_t size = 0; + int friend_index = 0; + int max_line = 0; + synctex_node_t node = NULL; + if(tag == 0) { + printf("SyncTeX Warning: No tag for %s\n",name); + return -1; + } + free(SYNCTEX_START); + SYNCTEX_CUR = SYNCTEX_END = SYNCTEX_START = NULL; + max_line = line < INT_MAX-scanner->number_of_lists ? line+scanner->number_of_lists:INT_MAX; + while(linenumber_of_lists); + if((node = (scanner->lists_of_friends)[friend_index])) { + do { + if((synctex_node_type(node)>=synctex_node_type_boundary) + && (tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if(SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + if(SYNCTEX_START == NULL) { + /* We did not find any matching boundary, retry with glue or kern */ + node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */ + do { + if((synctex_node_type(node)>=synctex_node_type_kern) + && (tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if(SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + if(SYNCTEX_START == NULL) { + /* We did not find any matching glue or kern, retry with boxes */ + node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */ + do { + if((tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if(SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + } + } + SYNCTEX_END = SYNCTEX_CUR; + /* Now reverse the order to have nodes in display order, and keep just a few nodes */ + if((SYNCTEX_START) && (SYNCTEX_END)) + { + synctex_node_t * start_ref = (synctex_node_t *)SYNCTEX_START; + synctex_node_t * end_ref = (synctex_node_t *)SYNCTEX_END; + end_ref -= 1; + while(start_ref < end_ref) { + node = *start_ref; + *start_ref = *end_ref; + *end_ref = node; + start_ref += 1; + end_ref -= 1; + } + /* Basically, we keep the first node for each parent. + * More precisely, we keep only nodes that are not descendants of + * their predecessor's parent. */ + start_ref = (synctex_node_t *)SYNCTEX_START; + end_ref = (synctex_node_t *)SYNCTEX_START; + next_end: + end_ref += 1; /* we allways have start_ref<= end_ref*/ + if(end_ref < (synctex_node_t *)SYNCTEX_END) { + node = *end_ref; + while((node = SYNCTEX_PARENT(node))) { + if(SYNCTEX_PARENT(*start_ref) == node) { + goto next_end; + } + } + start_ref += 1; + *start_ref = *end_ref; + goto next_end; + } + start_ref += 1; + SYNCTEX_END = (char *)start_ref; + } + SYNCTEX_CUR = NULL; + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); + } +# if defined(__SYNCTEX_STRONG_DISPLAY_QUERY__) + break; +# else + ++line; +# endif + } + return 0; +} + +synctex_node_t synctex_next_result(synctex_scanner_t scanner) { + if(NULL == SYNCTEX_CUR) { + SYNCTEX_CUR = SYNCTEX_START; + } else { + SYNCTEX_CUR+=sizeof(synctex_node_t); + } + if(SYNCTEX_CUR= scanner->unit) {/* scanner->unit must be >0 */ + return 0; + } + /* Convert the given point to scanner integer coordinates */ + hitPoint.h = (h-scanner->x_offset)/scanner->unit; + hitPoint.v = (v-scanner->y_offset)/scanner->unit; + /* We will store in the scanner's buffer the result of the query. */ + free(SYNCTEX_START); + SYNCTEX_START = SYNCTEX_END = SYNCTEX_CUR = NULL; + /* Find the proper sheet */ + sheet = scanner->sheet; + while((sheet) && SYNCTEX_PAGE(sheet) != page) { + sheet = SYNCTEX_SIBLING(sheet); + } + if(NULL == sheet) { + return -1; + } + /* Now sheet points to the sheet node with proper page number */ + /* Here is how we work: + * At first we do not consider the visible box dimensions. This will cover the most frequent cases. + * Then we try with the visible box dimensions. + * We try to find a non void box containing the hit point. + * We browse all the horizontal boxes until we find one containing the hit point. */ + if((node = SYNCTEX_NEXT_HORIZ_BOX(sheet))) { + do { + if(_synctex_point_in_box(hitPoint,node,synctex_YES)) { + /* Maybe the hitPoint belongs to a contained vertical box. */ +end: + /* This trick is for catching overlapping boxes */ + if((other_node = SYNCTEX_NEXT_HORIZ_BOX(node))) { + do { + if(_synctex_point_in_box(hitPoint,other_node,synctex_YES)) { + node = _synctex_smallest_container(other_node,node); + } + } while((other_node = SYNCTEX_NEXT_HORIZ_BOX(other_node))); + } + /* node is the smallest horizontal box that contains hitPoint. */ + if((bestContainer = _synctex_eq_deepest_container(hitPoint,node,synctex_YES))) { + node = bestContainer; + } + _synctex_eq_get_closest_children_in_box(hitPoint,node,&bestNodes,&bestDistances,synctex_YES); + if(bestNodes.right && bestNodes.left) { + if((SYNCTEX_TAG(bestNodes.right)!=SYNCTEX_TAG(bestNodes.left)) + || (SYNCTEX_LINE(bestNodes.right)!=SYNCTEX_LINE(bestNodes.left)) + || (SYNCTEX_COLUMN(bestNodes.right)!=SYNCTEX_COLUMN(bestNodes.left))) { + if((SYNCTEX_START = malloc(2*sizeof(synctex_node_t)))) { + if(bestDistances.left>bestDistances.right) { + ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.right; + ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.left; + } else { + ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.left; + ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.right; + } + SYNCTEX_END = SYNCTEX_START + 2*sizeof(synctex_node_t); + SYNCTEX_CUR = NULL; + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); + } + return SYNCTEX_STATUS_ERROR; + } + /* both nodes have the same input coordinates + * We choose the one closest to the hit point */ + if(bestDistances.left>bestDistances.right) { + bestNodes.left = bestNodes.right; + } + bestNodes.right = NULL; + } else if(bestNodes.right) { + bestNodes.left = bestNodes.right; + } else if(!bestNodes.left){ + bestNodes.left = node; + } + if((SYNCTEX_START = malloc(sizeof(synctex_node_t)))) { + * (synctex_node_t *)SYNCTEX_START = bestNodes.left; + SYNCTEX_END = SYNCTEX_START + sizeof(synctex_node_t); + SYNCTEX_CUR = NULL; + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); + } + return SYNCTEX_STATUS_ERROR; + } + } while ((node = SYNCTEX_NEXT_HORIZ_BOX(node))); + /* All the horizontal boxes have been tested, + * None of them contains the hit point. + */ + } + /* We are not lucky */ + if((node = SYNCTEX_CHILD(sheet))) { + goto end; + } + return 0; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Utilities +# endif + +int _synctex_bail(void) { + _synctex_error("SyncTeX ERROR\n"); + return -1; +} +/* Rougly speaking, this is: + * node's h coordinate - hitPoint's h coordinate. + * If node is to the right of the hit point, then this distance is positive, + * if node is to the left of the hit point, this distance is negative.*/ +int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible); +int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { + if(node) { + int min,med,max; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = visible?SYNCTEX_HORIZ_V(node):SYNCTEX_HORIZ(node); + max = min + (visible?SYNCTEX_ABS_WIDTH_V(node):SYNCTEX_ABS_WIDTH(node)); + /* We allways have min <= max */ + if(hitPoint.h 0 */ + } else if (hitPoint.h>max) { + return max - hitPoint.h; /* regions 3+6+9, result is < 0 */ + } else { + return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. + * For these boxes, no visible dimension available */ + min = SYNCTEX_HORIZ(node); + max = min + SYNCTEX_ABS_WIDTH(node); + /* We allways have min <= max */ + if(hitPoint.h 0 */ + } else if (hitPoint.h>max) { + return max - hitPoint.h; /* regions 3+6+9, result is < 0 */ + } else { + return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_kern: + /* IMPORTANT NOTICE: the location of the kern is recorded AFTER the move. + * The distance to the kern is very special, + * in general, there is no text material in the kern, + * this is why we compute the offset relative to the closest edge of the kern.*/ + max = SYNCTEX_WIDTH(node); + if(max<0) { + min = SYNCTEX_HORIZ(node); + max = min - max; + } else { + min = -max; + max = SYNCTEX_HORIZ(node); + min += max; + } + med = (min+max)/2; + /* positive kern: '.' means text, '>' means kern offset + * ............. + * min>>>>med>>>>max + * ............... + * negative kern: '.' means text, '<' means kern offset + * ............................ + * min<<<max) { + return max - hitPoint.h - 1; /* same kind of penalty */ + } else if (hitPoint.h>med) { + /* do things like if the node had 0 width and was placed at the max edge + 1*/ + return max - hitPoint.h + 1; /* positive, the kern is to the right of the hitPoint */ + } else { + return min - hitPoint.h - 1; /* negative, the kern is to the left of the hitPoint */ + } + case synctex_node_type_glue: + case synctex_node_type_math: + return SYNCTEX_HORIZ(node) - hitPoint.h; + } + } + return INT_MAX;/* We always assume that the node is faraway to the right*/ +} +/* Rougly speaking, this is: + * node's v coordinate - hitPoint's v coordinate. + * If node is at the top of the hit point, then this distance is positive, + * if node is at the bottom of the hit point, this distance is negative.*/ +int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible); +int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible) { +# ifdef __DARWIN_UNIX03 +# pragma unused(visible) +# endif + if(node) { + int min,max; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = SYNCTEX_VERT_V(node); + max = min + SYNCTEX_ABS_DEPTH_V(node); + min -= SYNCTEX_ABS_HEIGHT_V(node); + /* We allways have min <= max */ + if(hitPoint.v 0 */ + } else if (hitPoint.v>max) { + return max - hitPoint.v; /* regions 7+8+9, result is < 0 */ + } else { + return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = SYNCTEX_VERT(node); + max = min + SYNCTEX_ABS_DEPTH(node); + min -= SYNCTEX_ABS_HEIGHT(node); + /* We allways have min <= max */ + if(hitPoint.v 0 */ + } else if (hitPoint.v>max) { + return max - hitPoint.v; /* regions 7+8+9, result is < 0 */ + } else { + return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_kern: + case synctex_node_type_glue: + case synctex_node_type_math: + return SYNCTEX_VERT(node) - hitPoint.v; + } + } + return INT_MAX;/* We always assume that the node is faraway to the top*/ +} + +SYNCTEX_INLINE static synctex_node_t _synctex_smallest_container(synctex_node_t node, synctex_node_t other_node) { + float height, other_height; + if(SYNCTEX_ABS_WIDTH(node)SYNCTEX_ABS_WIDTH(other_node)) { + return other_node; + } + height = SYNCTEX_ABS_DEPTH(node) + SYNCTEX_ABS_HEIGHT(node); + other_height = SYNCTEX_ABS_DEPTH(other_node) + SYNCTEX_ABS_HEIGHT(other_node); + if(heightother_height) { + return other_node; + } + return node; +} + +synctex_bool_t _synctex_point_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { + if(node) { + if(0 == _synctex_point_h_distance(hitPoint,node,visible) + && 0 == _synctex_point_v_distance(hitPoint,node,visible)) { + return synctex_YES; + } + } + return synctex_NO; +} + +int _synctex_node_distance_to_point(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { +# ifdef __DARWIN_UNIX03 +# pragma unused(visible) +# endif + int result = INT_MAX; /* when the distance is meaning less (sheet, input...) */ + if(node) { + int minH,maxH,minV,maxV; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + * In each region, there is a different formula. + * In the end we have a continuous distance which may not be a mathematical distance but who cares. */ + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_hbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative widths. */ + minH = SYNCTEX_HORIZ(node); + maxH = minH + SYNCTEX_ABS_WIDTH(node); + minV = SYNCTEX_VERT(node); + maxV = minV + SYNCTEX_ABS_DEPTH(node); + minV -= SYNCTEX_ABS_HEIGHT(node); + /* In what region is the point hitPoint=(H,V) ? */ + if(hitPoint.vminV) { + result = hitPoint.v - minV + minH - hitPoint.h; + } else { + result = minV - hitPoint.v + minH - hitPoint.h; + } + } else if (hitPoint.h>maxH) { + if(hitPoint.v>minV) { + result = hitPoint.v - minV + hitPoint.h - maxH; + } else { + result = minV - hitPoint.v + hitPoint.h - maxH; + } + } else if(hitPoint.v>minV) { + result = hitPoint.v - minV; + } else { + result = minV - hitPoint.v; + } + break; + case synctex_node_type_glue: + case synctex_node_type_math: + minH = SYNCTEX_HORIZ(node); + minV = SYNCTEX_VERT(node); + if(hitPoint.hminV) { + result = hitPoint.v - minV + minH - hitPoint.h; + } else { + result = minV - hitPoint.v + minH - hitPoint.h; + } + } else if(hitPoint.v>minV) { + result = hitPoint.v - minV + hitPoint.h - minH; + } else { + result = minV - hitPoint.v + hitPoint.h - minH; + } + break; + } + } + return result; +} + +static synctex_node_t _synctex_eq_deepest_container(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) { + if(node) { + synctex_node_t result = NULL; + synctex_node_t child = NULL; + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + /* test the deep nodes first */ + if((child = SYNCTEX_CHILD(node))) { + do { + if((result = _synctex_eq_deepest_container(hitPoint,child,visible))) { + return result; + } + } while((child = SYNCTEX_SIBLING(child))); + } + /* is the hit point inside the box? */ + if(_synctex_point_in_box(hitPoint,node,visible)) { + /* for vboxes we try to use some node inside. + * Walk through the list of siblings until we find the closest one. + * Only consider siblings with children. */ + if((node->class->type == synctex_node_type_vbox) && (child = SYNCTEX_CHILD(node))) { + int bestDistance = INT_MAX; + do { + if(SYNCTEX_CHILD(child)) { + int distance = _synctex_node_distance_to_point(hitPoint,child,visible); + if(distance < bestDistance) { + bestDistance = distance; + node = child; + } + } + } while((child = SYNCTEX_SIBLING(child))); + } + return node; + } + } + } + return NULL; +} + +/* Compares the locations of the hitPoint with the locations of the various nodes contained in the box. + * As it is an horizontal box, we only compare horizontal coordinates. */ +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible); +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible) { + int result = 0; + if((node = SYNCTEX_CHILD(node))) { + do { + int off7 = _synctex_point_h_distance(hitPoint,node,visible); + if(off7 > 0) { + /* node is to the right of the hit point. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if(bestDistancesRef->right > off7) { + bestDistancesRef->right = off7; + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } else if(bestDistancesRef->right == off7 && bestNodesRef->right) { + if(SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } + } + } else if(off7 == 0) { + /* hitPoint is inside node. */ + bestDistancesRef->left = bestDistancesRef->right = 0; + bestNodesRef->left = node; + bestNodesRef->right = NULL; + result |= SYNCTEX_MASK_LEFT; + } else { /* here off7 < 0, hitPoint is to the right of node */ + off7 = -off7; + if(bestDistancesRef->left > off7) { + bestDistancesRef->left = off7; + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } else if(bestDistancesRef->left == off7 && bestNodesRef->left) { + if(SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } + } + } + } while((node = SYNCTEX_SIBLING(node))); + if(result & SYNCTEX_MASK_LEFT) { + /* the left node is new, try to narrow the result */ + if((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + if((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + } + if(result & SYNCTEX_MASK_RIGHT) { + /* the right node is new, try to narrow the result */ + if((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + if((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + } + } + return result; +} +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible); +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) { + int result = 0; + if((node = SYNCTEX_CHILD(node))) { + do { + int off7 = _synctex_point_v_distance(hitPoint,node,visible);/* this is what makes the difference with the h version above */ + if(off7 > 0) { + /* node is to the top of the hit point (below because TeX is oriented from top to bottom. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if(bestDistancesRef->right > off7) { + bestDistancesRef->right = off7; + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } else if(bestDistancesRef->right == off7 && bestNodesRef->right) { + if(SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } + } + } else if(off7 == 0) { + bestDistancesRef->left = bestDistancesRef->right = 0; + bestNodesRef->left = node; + bestNodesRef->right = NULL; + result |= SYNCTEX_MASK_LEFT; + } else { /* here off7 < 0 */ + off7 = -off7; + if(bestDistancesRef->left > off7) { + bestDistancesRef->left = off7; + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } else if(bestDistancesRef->left == off7 && bestNodesRef->left) { + if(SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } + } + } + } while((node = SYNCTEX_SIBLING(node))); + if(result & SYNCTEX_MASK_LEFT) { + /* the left node is new, try to narrow the result */ + if((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + if((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + } + if(result & SYNCTEX_MASK_RIGHT) { + /* the right node is new, try to narrow the result */ + if((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + if((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + } + } + return result; +} +SYNCTEX_INLINE static int _synctex_eq_get_closest_children_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) { + if(node) { + switch(node->class->type) { + case synctex_node_type_hbox: + return __synctex_eq_get_closest_children_in_hbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible); + case synctex_node_type_vbox: + return __synctex_eq_get_closest_children_in_vbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible); + } + } + return 0; +} + +SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible); +SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible) { + synctex_node_t best_node = NULL; + if((node = SYNCTEX_CHILD(node))) { + do { + int distance = _synctex_node_distance_to_point(hitPoint,node,visible); + synctex_node_t candidate = NULL; + if(distance<=*distanceRef) { + *distanceRef = distance; + best_node = node; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + if((candidate = __synctex_eq_closest_child(hitPoint,node,distanceRef,visible))) { + best_node = candidate; + } + } + } while((node = SYNCTEX_SIBLING(node))); + } + return best_node; +} +SYNCTEX_INLINE static synctex_node_t _synctex_eq_closest_child(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) { + if(node) { + switch(node->class->type) { + case synctex_node_type_hbox: + case synctex_node_type_vbox: + { + int best_distance = INT_MAX; + synctex_node_t best_node = __synctex_eq_closest_child(hitPoint,node,&best_distance,visible); + if((best_node)) { + synctex_node_t child = NULL; + switch(best_node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + if((child = SYNCTEX_CHILD(best_node))) { + best_distance = _synctex_node_distance_to_point(hitPoint,child,visible); + while((child = SYNCTEX_SIBLING(child))) { + int distance = _synctex_node_distance_to_point(hitPoint,child,visible); + if(distance<=best_distance) { + best_distance = distance; + best_node = child; + } + } + } + } + } + return best_node; + } + } + } + return NULL; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Updater +# endif + +typedef int (*synctex_fprintf_t)(void *, const char * , ...); /* print formatted to either FILE * or gzFile */ + +# define SYNCTEX_BITS_PER_BYTE 8 + +struct __synctex_updater_t { + void *file; /* the foo.synctex or foo.synctex.gz I/O identifier */ + synctex_fprintf_t fprintf; /* either fprintf or gzprintf */ + int length; /* the number of chars appended */ + struct _flags { + unsigned int no_gz:1; /* Whether zlib is used or not */ + unsigned int reserved:SYNCTEX_BITS_PER_BYTE*sizeof(int)-1; /* Align */ + } flags; +}; +# define SYNCTEX_FILE updater->file +# define SYNCTEX_NO_GZ ((updater->flags).no_gz) +# define SYNCTEX_fprintf (*(updater->fprintf)) + +synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * build_directory) { + synctex_updater_t updater = NULL; + char * synctex = NULL; + synctex_io_mode_t io_mode = synctex_io_mode_read; + const char * mode; + /* prepare the updater */ + updater = (synctex_updater_t)_synctex_malloc(sizeof(synctex_updater_t)); + if(NULL == updater) { + _synctex_error("! synctex_updater_new_with_file: malloc problem"); + return NULL; + } + if(_synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_NO,&io_mode) + && _synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_YES,&io_mode)) { +return_on_error: + free(updater); + return NULL; + } + /* OK, the file exists */ + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + SYNCTEX_NO_GZ = io_mode%2?synctex_NO:synctex_YES; + mode = synctex_io_modes[io_mode+synctex_io_mode_append];/* either "a" or "ab", depending on the file extension */ + if(SYNCTEX_NO_GZ) { + if(NULL == (SYNCTEX_FILE = (void *)fopen(synctex,mode))) { +no_write_error: + _synctex_error("! synctex_updater_new_with_file: Can't append to %s",synctex); + free(synctex); + goto return_on_error; + } + updater->fprintf = (synctex_fprintf_t)(&fprintf); + } else { + if(NULL == (SYNCTEX_FILE = (void *)gzopen(synctex,mode))) { + goto no_write_error; + } + updater->fprintf = (synctex_fprintf_t)(&gzprintf); + } + printf("SyncTeX: updating %s...",synctex); + free(synctex); + return updater; +} + + +void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification){ + if(NULL==updater) { + return; + } + if(magnification && strlen(magnification)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Magnification:%s\n",magnification); + } +} + +void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset){ + if(NULL==updater) { + return; + } + if(x_offset && strlen(x_offset)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"X Offset:%s\n",x_offset); + } +} + +void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset){ + if(NULL==updater) { + return; + } + if(y_offset && strlen(y_offset)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Y Offset:%s\n",y_offset); + } +} + +void synctex_updater_free(synctex_updater_t updater){ + if(NULL==updater) { + return; + } + if(updater->length>0) { + SYNCTEX_fprintf(SYNCTEX_FILE,"!%i\n",updater->length); + } + if (SYNCTEX_NO_GZ) { + fclose((FILE *)SYNCTEX_FILE); + } else { + gzclose((gzFile)SYNCTEX_FILE); + } + free(updater); + printf("... done.\n"); + return; +} diff --git a/cut-n-paste/synctex/synctex_parser.h b/cut-n-paste/synctex/synctex_parser.h new file mode 100644 index 00000000..b164b7fb --- /dev/null +++ b/cut-n-paste/synctex/synctex_parser.h @@ -0,0 +1,345 @@ +/* +Copyright (c) 2008, 2009, 2010 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Wed Jul 1 11:16:51 UTC 2009 + +Version: 1.12 +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Version 1 +Thu Jun 19 09:39:21 UTC 2008 + +*/ + +#ifndef __SYNCTEX_PARSER__ +# define __SYNCTEX_PARSER__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* synctex_node_t is the type for all synctex nodes. + * The synctex file is parsed into a tree of nodes, either sheet, boxes, math nodes... */ +typedef struct _synctex_node * synctex_node_t; + +/* The main synctex object is a scanner + * Its implementation is considered private. + * The basic workflow is + * - create a "synctex scanner" with the contents of a file + * - perform actions on that scanner like display or edit queries + * - free the scanner when the work is done + */ +typedef struct __synctex_scanner_t _synctex_scanner_t; +typedef _synctex_scanner_t * synctex_scanner_t; + +/* This is the designated method to create a new synctex scanner object. + * output is the pdf/dvi/xdv file associated to the synctex file. + * If necessary, it can be the tex file that originated the synctex file + * but this might cause problems if the \jobname has a custom value. + * Despite this method can accept a relative path in practice, + * you should only pass a full path name. + * The path should be encoded by the underlying file system, + * assuming that it is based on 8 bits characters, including UTF8, + * not 16 bits nor 32 bits. + * The last file extension is removed and replaced by the proper extension. + * Then the private method _synctex_scanner_new_with_contents_of_file is called. + * NULL is returned in case of an error or non existent file. + * Once you have a scanner, use the synctex_display_query and synctex_edit_query below. + * The new "build_directory" argument is available since version 1.5. + * It is the directory where all the auxiliary stuff is created. + * Sometimes, the synctex output file and the pdf, dvi or xdv files are not created in the same directory. + * This is the case in MikTeX (I will include this into TeX Live). + * This directory path can be nil, it will be ignored. + * It can be either absolute or relative to the directory of the output pdf (dvi or xdv) file. + * If no synctex file is found in the same directory as the output file, then we try to find one in the build directory. + * Please note that this new "build_directory" is provided as a convenient argument but should not be used. + * In fact, this is implempented as a work around of a bug in MikTeX where the synctex file does not follow the pdf file. + * The new "parse" argument is available since version 1.5. In general, use 1. + * Use 0 only if you do not want to parse the content but just check the existence. + */ +synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse); + +/* This is the designated method to delete a synctex scanner object. + * Frees all the memory, you must call it when you are finished with the scanner. + */ +void synctex_scanner_free(synctex_scanner_t scanner); + +/* Send this message to force the scanner to parse the contents of the synctex output file. + * Nothing is performed if the file was already parsed. + * In each query below, this message is sent, but if you need to access information more directly, + * you must be sure that the parsing did occur. + * Usage: + * if((my_scanner = synctex_scanner_parse(my_scanner))) { + * continue with my_scanner... + * } else { + * there was a problem + * } + */ +synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner); + +/* The main entry points. + * Given the file name, a line and a column number, synctex_display_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_display_query(scanner,name,line,column)>0) { + * synctex_node_t node; + * while((node = synctex_next_result(scanner))) { + * // do something with node + * ... + * } + * } + * + * For example, one can + * - highlight each resulting node in the output, using synctex_node_h and synctex_node_v + * - highlight all the rectangles enclosing those nodes, using synctex_box_... functions + * - highlight just the character using that information + * + * Given the page and the position in the page, synctex_edit_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_edit_query(scanner,page,h,v)>0) { + * synctex_node_t node; + * while(node = synctex_next_result(scanner)) { + * // do something with node + * ... + * } + * } + * + * For example, one can + * - highlight each resulting line in the input, + * - highlight just the character using that information + * + * page is 1 based + * h and v are coordinates in 72 dpi unit, relative to the top left corner of the page. + * If you make a new query, the result of the previous one is discarded. + * If one of this function returns a non positive integer, + * it means that an error occurred. + * + * Both methods are conservative, in the sense that matching is weak. + * If the exact column number is not found, there will be an answer with the whole line. + * + * Sumatra-PDF, Skim, iTeXMac2 and Texworks are examples of open source software that use this library. + * You can browse their code for a concrete implementation. + */ +int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column); +int synctex_edit_query(synctex_scanner_t scanner,int page,float h,float v); +synctex_node_t synctex_next_result(synctex_scanner_t scanner); + +/* Display all the information contained in the scanner object. + * If the records are too numerous, only the first ones are displayed. + * This is mainly for informatinal purpose to help developers. + */ +void synctex_scanner_display(synctex_scanner_t scanner); + +/* The x and y offset of the origin in TeX coordinates. The magnification + These are used by pdf viewers that want to display the real box size. + For example, getting the horizontal coordinates of a node would require + synctex_node_box_h(node)*synctex_scanner_magnification(scanner)+synctex_scanner_x_offset(scanner) + Getting its TeX width would simply require + synctex_node_box_width(node)*synctex_scanner_magnification(scanner) + but direct methods are available for that below. + */ +int synctex_scanner_x_offset(synctex_scanner_t scanner); +int synctex_scanner_y_offset(synctex_scanner_t scanner); +float synctex_scanner_magnification(synctex_scanner_t scanner); + +/* Managing the input file names. + * Given a tag, synctex_scanner_get_name will return the corresponding file name. + * Conversely, given a file name, synctex_scanner_get_tag will retur, the corresponding tag. + * The file name must be the very same as understood by TeX. + * For example, if you \input myDir/foo.tex, the file name is myDir/foo.tex. + * No automatic path expansion is performed. + * Finally, synctex_scanner_input is the first input node of the scanner. + * To browse all the input node, use a loop like + * + * if((input_node = synctex_scanner_input(scanner))){ + * do { + * blah + * } while((input_node=synctex_node_sibling(input_node))); + * } + * + * The output is the name that was used to create the scanner. + * The synctex is the real name of the synctex file, + * it was obtained from output by setting the proper file extension. + */ +const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag); +int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name); +synctex_node_t synctex_scanner_input(synctex_scanner_t scanner); +const char * synctex_scanner_get_output(synctex_scanner_t scanner); +const char * synctex_scanner_get_synctex(synctex_scanner_t scanner); + +/* Browsing the nodes + * parent, child and sibling are standard names for tree nodes. + * The parent is one level higher, the child is one level deeper, + * and the sibling is at the same level. + * The sheet of a node is the first ancestor, it is of type sheet. + * A node and its sibling have the same parent. + * A node is the parent of its child. + * A node is either the child of its parent, + * or belongs to the sibling chain of its parent's child. + * The next node is either the child, the sibling or the parent's sibling, + * unless the parent is a sheet. + * This allows to navigate through all the nodes of a given sheet node: + * + * synctex_node_t node = sheet; + * while((node = synctex_node_next(node))) { + * // do something with node + * } + * + * With synctex_sheet_content, you can retrieve the sheet node given the page. + * The page is 1 based, according to TeX standards. + * Conversely synctex_node_sheet allows to retrieve the sheet containing a given node. + */ +synctex_node_t synctex_node_parent(synctex_node_t node); +synctex_node_t synctex_node_sheet(synctex_node_t node); +synctex_node_t synctex_node_child(synctex_node_t node); +synctex_node_t synctex_node_sibling(synctex_node_t node); +synctex_node_t synctex_node_next(synctex_node_t node); +synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page); + +/* These are the types of the synctex nodes */ +typedef enum { + synctex_node_type_error = 0, + synctex_node_type_input, + synctex_node_type_sheet, + synctex_node_type_vbox, + synctex_node_type_void_vbox, + synctex_node_type_hbox, + synctex_node_type_void_hbox, + synctex_node_type_kern, + synctex_node_type_glue, + synctex_node_type_math, + synctex_node_type_boundary, + synctex_node_number_of_types +} synctex_node_type_t; + +/* synctex_node_type gives the type of a given node, + * synctex_node_isa gives the same information as a human readable text. */ +synctex_node_type_t synctex_node_type(synctex_node_t node); +const char * synctex_node_isa(synctex_node_t node); + +/* This is primarily used for debugging purpose. + * The second one logs information for the node and recursively displays information for its next node */ +void synctex_node_log(synctex_node_t node); +void synctex_node_display(synctex_node_t node); + +/* Given a node, access to its tag, line and column. + * The line and column numbers are 1 based. + * The latter is not yet fully supported in TeX, the default implementation returns 0 which means the whole line. + * When the tag is known, the scanner of the node will give the corresponding file name. + * When the tag is known, the scanner of the node will give the name. + */ +int synctex_node_tag(synctex_node_t node); +int synctex_node_line(synctex_node_t node); +int synctex_node_column(synctex_node_t node); + +/* This is the page where the node appears. + * This is a 1 based index as given by TeX. + */ +int synctex_node_page(synctex_node_t node); + +/* For quite all nodes, horizontal, vertical coordinates, and width. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + */ +int synctex_node_h(synctex_node_t node); +int synctex_node_v(synctex_node_t node); +int synctex_node_width(synctex_node_t node); + +/* For all nodes, dimensions of the enclosing box. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + * A box is enclosing itself. + */ +int synctex_node_box_h(synctex_node_t node); +int synctex_node_box_v(synctex_node_t node); +int synctex_node_box_width(synctex_node_t node); +int synctex_node_box_height(synctex_node_t node); +int synctex_node_box_depth(synctex_node_t node); + +/* For quite all nodes, horizontal, vertical coordinates, and width. + * The visible dimensions are bigger than real ones to compensate 0 width boxes + * that do contain nodes. + * These are expressed in page coordinates, with origin at the top left corner. + * A box is enclosing itself. + */ +float synctex_node_visible_h(synctex_node_t node); +float synctex_node_visible_v(synctex_node_t node); +float synctex_node_visible_width(synctex_node_t node); +/* For all nodes, visible dimensions of the enclosing box. + * A box is enclosing itself. + * The visible dimensions are bigger than real ones to compensate 0 width boxes + * that do contain nodes. + */ +float synctex_node_box_visible_h(synctex_node_t node); +float synctex_node_box_visible_v(synctex_node_t node); +float synctex_node_box_visible_width(synctex_node_t node); +float synctex_node_box_visible_height(synctex_node_t node); +float synctex_node_box_visible_depth(synctex_node_t node); + +/* The main synctex updater object. + * This object is used to append information to the synctex file. + * Its implementation is considered private. + * It is used by the synctex command line tool to take into account modifications + * that could occur while postprocessing files by dvipdf like filters. + */ +typedef struct __synctex_updater_t _synctex_updater_t; +typedef _synctex_updater_t * synctex_updater_t; + +/* Designated initializer. + * Once you are done with your whole job, + * free the updater */ +synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * directory); + +/* Use the next functions to append records to the synctex file, + * no consistency tests made on the arguments */ +void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification); +void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset); +void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset); + +/* You MUST free the updater, once everything is properly appended */ +void synctex_updater_free(synctex_updater_t updater); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cut-n-paste/synctex/synctex_parser_utils.c b/cut-n-paste/synctex/synctex_parser_utils.c new file mode 100644 index 00000000..0aef5777 --- /dev/null +++ b/cut-n-paste/synctex/synctex_parser_utils.c @@ -0,0 +1,462 @@ +/* +Copyright (c) 2008, 2009 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Wed Nov 4 11:52:35 UTC 2009 + +Version: 1.9 +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +/* In this file, we find all the functions that may depend on the operating system. */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) +#define SYNCTEX_WINDOWS 1 +#endif + +#ifdef _WIN32_WINNT_WINXP +#define SYNCTEX_RECENT_WINDOWS 1 +#endif + +#ifdef SYNCTEX_WINDOWS +#include +#endif + +void *_synctex_malloc(size_t size) { + void * ptr = malloc(size); + if(ptr) { +/* There used to be a switch to use bzero because it is more secure. JL */ + memset(ptr,0, size); + } + return (void *)ptr; +} + +int _synctex_error(const char * reason,...) { + va_list arg; + int result; + va_start (arg, reason); +# ifdef SYNCTEX_RECENT_WINDOWS + {/* This code is contributed by William Blum. + As it does not work on some older computers, + the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one. + According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx + Minimum supported client Windows 2000 Professional + Minimum supported server Windows 2000 Server + People running Windows 2K standard edition will not have OutputDebugStringA. + JL.*/ + char *buff; + size_t len; + OutputDebugStringA("SyncTeX ERROR: "); + len = _vscprintf(reason, arg) + 1; + buff = (char*)malloc( len * sizeof(char) ); + result = vsprintf(buff, reason, arg) +strlen("SyncTeX ERROR: "); + OutputDebugStringA(buff); + OutputDebugStringA("\n"); + free(buff); + } +# else + result = fprintf(stderr,"SyncTeX ERROR: "); + result += vfprintf(stderr, reason, arg); + result += fprintf(stderr,"\n"); +# endif + va_end (arg); + return result; +} + +/* strip the last extension of the given string, this string is modified! */ +void _synctex_strip_last_path_extension(char * string) { + if(NULL != string){ + char * last_component = NULL; + char * last_extension = NULL; + char * next = NULL; + /* first we find the last path component */ + if(NULL == (last_component = strstr(string,"/"))){ + last_component = string; + } else { + ++last_component; + while((next = strstr(last_component,"/"))){ + last_component = next+1; + } + } +# ifdef SYNCTEX_WINDOWS + /* On Windows, the '\' is also a path separator. */ + while((next = strstr(last_component,"\\"))){ + last_component = next+1; + } +# endif + /* then we find the last path extension */ + if((last_extension = strstr(last_component,"."))){ + ++last_extension; + while((next = strstr(last_extension,"."))){ + last_extension = next+1; + } + --last_extension;/* back to the "." */ + if(last_extension>last_component){/* filter out paths like ....my/dir/.hidden"*/ + last_extension[0] = '\0'; + } + } + } +} + +/* Compare two file names, windows is sometimes case insensitive... */ +synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) { +# if SYNCTEX_WINDOWS + /* On Windows, filename should be compared case insensitive. + * The characters '/' and '\' are both valid path separators. + * There will be a very serious problem concerning UTF8 because + * not all the characters must be toupper... + * I would like to have URL's instead of filenames. */ +next_character: + if(SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */ + if(!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */ + return synctex_NO; + } + } else if(SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */ + return synctex_NO; + } else if(toupper(*lhs) != toupper(*rhs)){/* uppercase do not match */ + return synctex_NO; + } else if (!*lhs) {/* lhs is at the end of the string */ + return *rhs ? synctex_NO : synctex_YES; + } else if(!*rhs) {/* rhs is at the end of the string but not lhs */ + return synctex_NO; + } + ++lhs; + ++rhs; + goto next_character; +# else + return 0 == strcmp(lhs,rhs)?synctex_YES:synctex_NO; +# endif +} + +synctex_bool_t _synctex_path_is_absolute(const char * name) { + if(!strlen(name)) { + return synctex_NO; + } +# if SYNCTEX_WINDOWS + if(strlen(name)>2) { + return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO; + } + return synctex_NO; +# else + return SYNCTEX_IS_PATH_SEPARATOR(name[0])?synctex_YES:synctex_NO; +# endif +} + +/* We do not take care of UTF-8 */ +const char * _synctex_last_path_component(const char * name) { + const char * c = name+strlen(name); + if(c>name) { + if(!SYNCTEX_IS_PATH_SEPARATOR(*c)) { + do { + --c; + if(SYNCTEX_IS_PATH_SEPARATOR(*c)) { + return c+1; + } + } while(c>name); + } + return c;/* the last path component is the void string*/ + } + return c; +} + +int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size) { + const char * lpc; + if(src && dest_ref) { +# define dest (*dest_ref) + dest = NULL; /* Default behavior: no change and sucess. */ + lpc = _synctex_last_path_component(src); + if(strlen(lpc)) { + if(strchr(lpc,' ') && lpc[0]!='"' && lpc[strlen(lpc)-1]!='"') { + /* We are in the situation where adding the quotes is allowed. */ + /* Time to add the quotes. */ + /* Consistency test: we must have dest+size>dest+strlen(dest)+2 + * or equivalently: strlen(dest)+20) { + char * result = NULL; + ++size; + /* Create the memory storage */ + if(NULL!=(result = (char *)malloc(size))) { + char * dest = result; + va_start (arg, first); + temp = first; + do { + if((size = strlen(temp))>0) { + /* There is something to merge */ + if(dest != strncpy(dest,temp,size)) { + _synctex_error("! _synctex_merge_strings: Copy problem"); + free(result); + result = NULL; + return NULL; + } + dest += size; + } + } while( (temp = va_arg(arg, const char *)) != NULL); + va_end(arg); + dest[0]='\0';/* Terminate the merged string */ + return result; + } + _synctex_error("! _synctex_merge_strings: Memory problem"); + return NULL; + } + return NULL; +} + +/* The purpose of _synctex_get_name is to find the name of the synctex file. + * There is a list of possible filenames from which we return the most recent one and try to remove all the others. + * With two runs of pdftex or xetex we are sure the the synctex file is really the most appropriate. + */ +int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_compress_mode_t * compress_mode_ref) +{ + if(output && synctex_name_ref && compress_mode_ref) { +# define synctex_name (*synctex_name_ref) +# define compress_mode (*compress_mode_ref) + /* If output is already absolute, we just have to manage the quotes and the compress mode */ + const char * basename = NULL; /* base name of output*/ + size_t size = 0; + /* Initialize the return values. */ + synctex_name = NULL; + compress_mode = synctex_compress_mode_none; + basename = _synctex_last_path_component(output); /* do not free, output is the owner. */ + /* Do we have a real base name ? */ + if((size = strlen(basename))>0) { + /* Yes, we do. */ + const char * temp = NULL; + char * corename = NULL; /* base name of output without path extension. */ + char * dirname = NULL; /* dir name of output */ + char * quoted_corename = NULL; + char * none = NULL; + char * gz = NULL; + char * quoted = NULL; + char * quoted_gz = NULL; + char * build = NULL; + char * build_gz = NULL; + char * build_quoted = NULL; + char * build_quoted_gz = NULL; + struct stat buf; + time_t time = 0; + /* Create corename: let temp point to the dot before the path extension of basename; + * We start form the \0 terminating character and scan the string upward until we find a dot. + * The first dot is not accepted. */ + temp = strrchr(basename,'.'); + size = temp - basename; + if(size>0) { + /* dot properly found, now create corename */ + if(NULL == (corename = (char *)malloc(size+1))) { + _synctex_error("! _synctex_get_name: Memory problem 1"); + return -1; + } + if(corename != strncpy(corename,basename,size)) { + _synctex_error("! _synctex_get_name: Copy problem 1"); + free(corename); + dirname = NULL; + return -2; + } + corename[size] = '\0'; + } else { + /* There is no path extension, + * Just make a copy of basename */ + corename = _synctex_merge_strings(basename); + } + /* corename is properly set up, owned by "self". */ + /* creating dirname. */ + size = strlen(output)-strlen(basename); + if(size>0) { + /* output contains more than one path component */ + if(NULL == (dirname = (char *)malloc(size+1))) { + _synctex_error("! _synctex_get_name: Memory problem"); + free(corename); + dirname = NULL; + return -1; + } + if(dirname != strncpy(dirname,output,size)) { + _synctex_error("! _synctex_get_name: Copy problem"); + free(dirname); + dirname = NULL; + free(corename); + dirname = NULL; + return -2; + } + dirname[size] = '\0'; + } + /* dirname is properly set up. It ends with a path separator, if non void. */ + /* creating quoted_corename. */ + if(strchr(corename,' ')) { + quoted_corename = _synctex_merge_strings("\"",corename,"\""); + } + /* quoted_corename is properly set up. */ + if(dirname &&strlen(dirname)>0) { + none = _synctex_merge_strings(dirname,corename,synctex_suffix,NULL); + if(quoted_corename && strlen(quoted_corename)>0) { + quoted = _synctex_merge_strings(dirname,quoted_corename,synctex_suffix,NULL); + } + } else { + none = _synctex_merge_strings(corename,synctex_suffix,NULL); + if(quoted_corename && strlen(quoted_corename)>0) { + quoted = _synctex_merge_strings(quoted_corename,synctex_suffix,NULL); + } + } + if(!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) { + temp = build_directory + size - 1; + if(_synctex_path_is_absolute(temp)) { + build = _synctex_merge_strings(build_directory,none,NULL); + if(quoted_corename && strlen(quoted_corename)>0) { + build_quoted = _synctex_merge_strings(build_directory,quoted,NULL); + } + } else { + build = _synctex_merge_strings(build_directory,"/",none,NULL); + if(quoted_corename && strlen(quoted_corename)>0) { + build_quoted = _synctex_merge_strings(build_directory,"/",quoted,NULL); + } + } + } + if(none) { + gz = _synctex_merge_strings(none,synctex_suffix_gz,NULL); + } + if(quoted) { + quoted_gz = _synctex_merge_strings(quoted,synctex_suffix_gz,NULL); + } + if(build) { + build_gz = _synctex_merge_strings(build,synctex_suffix_gz,NULL); + } + if(build_quoted) { + build_quoted_gz = _synctex_merge_strings(build_quoted,synctex_suffix_gz,NULL); + } + /* All the others names are properly set up... */ + /* retain the most recently modified file */ +# define TEST(FILENAME,COMPRESS_MODE) \ + if(FILENAME) {\ + if (stat(FILENAME, &buf)) { \ + free(FILENAME);\ + FILENAME = NULL;\ + } else { \ + if(buf.st_mtime>time) { \ + time=buf.st_mtime; \ + synctex_name = FILENAME; \ + compress_mode = COMPRESS_MODE; \ + } \ + } \ + } + TEST(none,synctex_compress_mode_none); + TEST(gz,synctex_compress_mode_gz); + TEST(quoted,synctex_compress_mode_none); + TEST(quoted_gz,synctex_compress_mode_gz); + TEST(build,synctex_compress_mode_none); + TEST(build_gz,synctex_compress_mode_gz); + TEST(build_quoted,synctex_compress_mode_none); + TEST(build_quoted_gz,synctex_compress_mode_gz); +# undef TEST + /* Free all the intermediate filenames, except the one that will be used as returned value. */ +# define CLEAN_AND_REMOVE(FILENAME) \ + if(FILENAME && (FILENAME!=synctex_name)) {\ + remove(FILENAME);\ + printf("synctex tool info: %s removed\n",FILENAME);\ + free(FILENAME);\ + FILENAME = NULL;\ + } + CLEAN_AND_REMOVE(none); + CLEAN_AND_REMOVE(gz); + CLEAN_AND_REMOVE(quoted); + CLEAN_AND_REMOVE(quoted_gz); + CLEAN_AND_REMOVE(build); + CLEAN_AND_REMOVE(build_gz); + CLEAN_AND_REMOVE(build_quoted); + CLEAN_AND_REMOVE(build_quoted_gz); +# undef CLEAN_AND_REMOVE + return 0; + } + return -1;/* bad argument */ +# undef synctex_name +# undef compress_mode + } + return -2; +} + diff --git a/cut-n-paste/synctex/synctex_parser_utils.h b/cut-n-paste/synctex/synctex_parser_utils.h new file mode 100644 index 00000000..e28ff58a --- /dev/null +++ b/cut-n-paste/synctex/synctex_parser_utils.h @@ -0,0 +1,123 @@ +/* +Copyright (c) 2008, 2009 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Version: 1.8 +Latest Revision: Wed Jul 1 11:16:01 UTC 2009 +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +/* The utilities declared here are subject to conditional implementation. + * All the operating system special stuff goes here. + * The problem mainly comes from file name management: path separator, encoding... + */ + +# define synctex_bool_t int +# define synctex_YES -1 +# define synctex_NO 0 + +#ifndef __SYNCTEX_PARSER_UTILS__ +# define __SYNCTEX_PARSER_UTILS__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +# if _WIN32 +# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c) +# else +# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c) +# endif + +/* This custom malloc functions initializes to 0 the newly allocated memory. + * There is no bzero function on windows. */ +void *_synctex_malloc(size_t size); + +/* This is used to log some informational message to the standard error stream. + * On Windows, the stderr stream is not exposed and another method is used. + * The return value is the number of characters printed. */ +int _synctex_error(const char * reason,...); + +/* strip the last extension of the given string, this string is modified! + * This function depends on the OS because the path separator may differ. + * This should be discussed more precisely. */ +void _synctex_strip_last_path_extension(char * string); + +/* Compare two file names, windows is sometimes case insensitive... + * The given strings may differ stricto sensu, but represent the same file name. + * It might not be the real way of doing things. + * The return value is an undefined non 0 value when the two file names are equivalent. + * It is 0 otherwise. */ +synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs); + +/* Description forthcoming.*/ +synctex_bool_t _synctex_path_is_absolute(const char * name); + +/* Description forthcoming...*/ +const char * _synctex_last_path_component(const char * name); + +/* If the core of the last path component of src is not already enclosed with double quotes ('"') + * and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added. + * In all other cases, no destination buffer is created and the src is not copied. + * 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter. + * This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces + * were not managed in a standard way. + * On success, the caller owns the buffer pointed to by dest_ref (is any) and + * is responsible of freeing the memory when done. + * The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/ +int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size); + +/* These are the possible extensions of the synctex file */ +extern const char * synctex_suffix; +extern const char * synctex_suffix_gz; + +typedef enum { + synctex_io_mode_read = 0, + synctex_io_mode_append = 2 +} synctex_io_mode_t; + +typedef enum { + synctex_compress_mode_none = 0, + synctex_compress_mode_gz = 1 +} synctex_compress_mode_t; + +int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_compress_mode_t * compress_mode_ref); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cut-n-paste/toolbar-editor/Makefile.am b/cut-n-paste/toolbar-editor/Makefile.am new file mode 100644 index 00000000..b3e843ec --- /dev/null +++ b/cut-n-paste/toolbar-editor/Makefile.am @@ -0,0 +1,108 @@ +EGGSOURCES = \ + egg-editable-toolbar.c \ + egg-toolbars-model.c \ + egg-toolbar-editor.c + +EGGHEADERS = \ + egg-editable-toolbar.h \ + egg-toolbars-model.h \ + egg-toolbar-editor.h + +noinst_HEADERS = \ + $(EGGHEADERS) \ + eggmarshalers.h + +noinst_LTLIBRARIES = libtoolbareditor.la + +libtoolbareditor_la_SOURCES = \ + $(BUILT_SOURCES) \ + $(EGGSOURCES) \ + $(EGGHEADERS) + +libtoolbareditor_la_CPPFLAGS = \ + $(AM_CPPFLAGS) + +libtoolbareditor_la_CFLAGS = \ + $(SHELL_CORE_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + -DCURSOR_DIR=\"$(pkgdatadir)\" \ + $(AM_CFLAGS) + +BUILT_SOURCES = \ + eggmarshalers.c \ + eggmarshalers.h \ + eggtypebuiltins.c \ + eggtypebuiltins.h + +stamp_files = \ + stamp-eggmarshalers.c \ + stamp-eggmarshalers.h \ + stamp-eggtypebuiltins.c \ + stamp-eggtypebuiltins.h + +eggmarshalers.h: stamp-eggmarshalers.h + @true +stamp-eggmarshalers.h: eggmarshalers.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=_egg_marshal $(srcdir)/eggmarshalers.list --header > eggmarshalers.h \ + && echo timestamp > $(@F) + +eggmarshalers.c: stamp-eggmarshalers.c + @true +stamp-eggmarshalers.c: eggmarshalers.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=_egg_marshal $(srcdir)/eggmarshalers.list --header --body > eggmarshalers.c \ + && echo timestamp > $(@F) + +eggtypebuiltins.c: stamp-eggtypebuiltins.c + @true +stamp-eggtypebuiltins.c: $(EGGHEADERS) + $(AM_V_GEN)( cd $(srcdir) && $(GLIB_MKENUMS) \ + --fhead "#include \n\n" \ + --fhead "#include \"eggtypebuiltins.h\"\n\n" \ + --fprod "\n/* enumerations from \"@filename@\" */" \ + --fprod "\n#include \"@filename@\"" \ + --vhead "static const G@Type@Value _@enum_name@_values[] = {" \ + --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ + --vtail " { 0, NULL, NULL }\n};\n\n" \ + --vtail "GType\n@enum_name@_get_type (void)\n{\n" \ + --vtail " static GType type = 0;\n\n" \ + --vtail " if (G_UNLIKELY (type == 0))\n" \ + --vtail " type = g_@type@_register_static (\"@EnumName@\", _@enum_name@_values);\n\n" \ + --vtail " return type;\n}\n\n" \ + $(^F) ) > xgen-$(@F) \ + && ( cmp -s xgen-$(@F) $(@F:stamp-%=%) || cp xgen-$(@F) $(@F:stamp-%=%) ) \ + && rm -f xgen-$(@F) \ + && echo timestamp > $(@F) + +eggtypebuiltins.h: stamp-eggtypebuiltins.h + @true +stamp-eggtypebuiltins.h: $(EGGHEADERS) + $(AM_V_GEN)( cd $(srcdir) && $(GLIB_MKENUMS) \ + --fhead "#ifndef __EGGTYPEBUILTINS_H__\n" \ + --fhead "#define __EGGTYPEBUILTINS_H__ 1\n\n" \ + --fhead "#include \n\n" \ + --fhead "G_BEGIN_DECLS\n\n" \ + --ftail "G_END_DECLS\n\n" \ + --ftail "#endif /* __EGGTYPEBUILTINS_H__ */\n" \ + --fprod "\n/* --- @filename@ --- */" \ + --eprod "#define EGG_TYPE_@ENUMSHORT@ @enum_name@_get_type()\n" \ + --eprod "GType @enum_name@_get_type (void);\n" \ + $(^F) ) > xgen-$(@F) \ + && ( cmp -s xgen-$(@F) $(@F:stamp-%=%) || cp xgen-$(@F) $(@F:stamp-%=%) ) \ + && rm -f xgen-$(@F) \ + && echo timestamp > $(@F) + +EXTRA_DIST = \ + eggmarshalers.list + +EGGFILES=$(EGGSOURCES) $(EGGHEADERS) +EGGDIR=$(srcdir)/../../../libegg/libegg + +regenerate-built-sources: + EGGFILES="$(EGGFILES) eggmarshalers.list" EGGDIR="$(EGGDIR)" $(top_srcdir)/cut-n-paste/update-from-egg.sh + +CLEANFILES = $(stamp_files) $(BUILT_SOURCES) +DISTCLEANFILES = $(stamp_files) $(BUILT_SOURCES) +MAINTAINERCLEANFILES = $(stamp_files) $(BUILT_SOURCES) + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/toolbar-editor/egg-editable-toolbar.c b/cut-n-paste/toolbar-editor/egg-editable-toolbar.c new file mode 100644 index 00000000..9193120a --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-editable-toolbar.c @@ -0,0 +1,1828 @@ +/* + * Copyright (C) 2003, 2004 Marco Pesenti Gritti + * Copyright (C) 2003, 2004, 2005 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "egg-editable-toolbar.h" +#include "egg-toolbars-model.h" +#include "egg-toolbar-editor.h" + +#include +#include +#include + +static GdkPixbuf * new_separator_pixbuf (void); + +#define MIN_TOOLBAR_HEIGHT 20 +#define EGG_ITEM_NAME "egg-item-name" +#define STOCK_DRAG_MODE "stock_drag-mode" + +static const GtkTargetEntry dest_drag_types[] = { + {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0}, +}; + +enum +{ + PROP_0, + PROP_TOOLBARS_MODEL, + PROP_UI_MANAGER, + PROP_POPUP_PATH, + PROP_SELECTED, + PROP_EDIT_MODE +}; + +enum +{ + ACTION_REQUEST, + LAST_SIGNAL +}; + +static guint egg_editable_toolbar_signals[LAST_SIGNAL] = { 0 }; + +#define EGG_EDITABLE_TOOLBAR_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EGG_TYPE_EDITABLE_TOOLBAR, EggEditableToolbarPrivate)) + +struct _EggEditableToolbarPrivate +{ + GtkUIManager *manager; + EggToolbarsModel *model; + guint edit_mode; + gboolean save_hidden; + GtkWidget *fixed_toolbar; + + GtkWidget *selected; + GtkActionGroup *actions; + + guint visibility_id; + GList *visibility_paths; + GPtrArray *visibility_actions; + + char *popup_path; + + guint dnd_pending; + GtkToolbar *dnd_toolbar; + GtkToolItem *dnd_toolitem; +}; + +G_DEFINE_TYPE (EggEditableToolbar, egg_editable_toolbar, GTK_TYPE_VBOX); + +static int +get_dock_position (EggEditableToolbar *etoolbar, + GtkWidget *dock) +{ + GList *l; + int result; + + l = gtk_container_get_children (GTK_CONTAINER (etoolbar)); + result = g_list_index (l, dock); + g_list_free (l); + + return result; +} + +static int +get_toolbar_position (EggEditableToolbar *etoolbar, GtkWidget *toolbar) +{ + return get_dock_position (etoolbar, gtk_widget_get_parent (toolbar)); +} + +static int +get_n_toolbars (EggEditableToolbar *etoolbar) +{ + GList *l; + int result; + + l = gtk_container_get_children (GTK_CONTAINER (etoolbar)); + result = g_list_length (l); + g_list_free (l); + + return result; +} + +static GtkWidget * +get_dock_nth (EggEditableToolbar *etoolbar, + int position) +{ + GList *l; + GtkWidget *result; + + l = gtk_container_get_children (GTK_CONTAINER (etoolbar)); + result = g_list_nth_data (l, position); + g_list_free (l); + + return result; +} + +static GtkWidget * +get_toolbar_nth (EggEditableToolbar *etoolbar, + int position) +{ + GList *l; + GtkWidget *dock; + GtkWidget *result; + + dock = get_dock_nth (etoolbar, position); + g_return_val_if_fail (dock != NULL, NULL); + + l = gtk_container_get_children (GTK_CONTAINER (dock)); + result = GTK_WIDGET (l->data); + g_list_free (l); + + return result; +} + +static GtkAction * +find_action (EggEditableToolbar *etoolbar, + const char *name) +{ + GList *l; + GtkAction *action = NULL; + + l = gtk_ui_manager_get_action_groups (etoolbar->priv->manager); + + g_return_val_if_fail (name != NULL, NULL); + + for (; l != NULL; l = l->next) + { + GtkAction *tmp; + + tmp = gtk_action_group_get_action (GTK_ACTION_GROUP (l->data), name); + if (tmp) + action = tmp; + } + + return action; +} + +static void +drag_data_delete_cb (GtkWidget *widget, + GdkDragContext *context, + EggEditableToolbar *etoolbar) +{ + int pos, toolbar_pos; + GtkWidget *parent; + + widget = gtk_widget_get_ancestor (widget, GTK_TYPE_TOOL_ITEM); + g_return_if_fail (widget != NULL); + g_return_if_fail (EGG_IS_EDITABLE_TOOLBAR (etoolbar)); + + parent = gtk_widget_get_parent (widget); + pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (parent), + GTK_TOOL_ITEM (widget)); + toolbar_pos = get_toolbar_position (etoolbar, parent); + + egg_toolbars_model_remove_item (etoolbar->priv->model, + toolbar_pos, pos); +} + +static void +drag_begin_cb (GtkWidget *widget, + GdkDragContext *context, + EggEditableToolbar *etoolbar) +{ + GtkAction *action; + gint flags; + + gtk_widget_hide (widget); + +#if GTK_CHECK_VERSION (2, 16, 0) + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (widget)); +#else + action = gtk_widget_get_action (widget); +#endif + + if (action == NULL) return; + + flags = egg_toolbars_model_get_name_flags (etoolbar->priv->model, + gtk_action_get_name (action)); + if (!(flags & EGG_TB_MODEL_NAME_INFINITE)) + { + flags &= ~EGG_TB_MODEL_NAME_USED; + egg_toolbars_model_set_name_flags (etoolbar->priv->model, + gtk_action_get_name (action), + flags); + } +} + +static void +drag_end_cb (GtkWidget *widget, + GdkDragContext *context, + EggEditableToolbar *etoolbar) +{ + GtkAction *action; + gint flags; + + if (gtk_widget_get_parent (widget) != NULL) + { + gtk_widget_show (widget); + +#if GTK_CHECK_VERSION (2, 16, 0) + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (widget)); +#else + action = gtk_widget_get_action (widget); +#endif + + if (action == NULL) return; + + flags = egg_toolbars_model_get_name_flags (etoolbar->priv->model, + gtk_action_get_name (action)); + if (!(flags & EGG_TB_MODEL_NAME_INFINITE)) + { + flags |= EGG_TB_MODEL_NAME_USED; + egg_toolbars_model_set_name_flags (etoolbar->priv->model, + gtk_action_get_name (action), + flags); + } + } +} + +static void +drag_data_get_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint32 time, + EggEditableToolbar *etoolbar) +{ + EggToolbarsModel *model; + const char *name; + char *data; + GdkAtom target; + + g_return_if_fail (EGG_IS_EDITABLE_TOOLBAR (etoolbar)); + model = egg_editable_toolbar_get_model (etoolbar); + + name = g_object_get_data (G_OBJECT (widget), EGG_ITEM_NAME); + if (name == NULL) + { + name = g_object_get_data (G_OBJECT (gtk_widget_get_parent (widget)), EGG_ITEM_NAME); + g_return_if_fail (name != NULL); + } + + target = gtk_selection_data_get_target (selection_data); + data = egg_toolbars_model_get_data (model, target, name); + if (data != NULL) + { + gtk_selection_data_set (selection_data, target, 8, (unsigned char *)data, strlen (data)); + g_free (data); + } +} + +static void +move_item_cb (GtkAction *action, + EggEditableToolbar *etoolbar) +{ + GtkWidget *toolitem = gtk_widget_get_ancestor (egg_editable_toolbar_get_selected (etoolbar), GTK_TYPE_TOOL_ITEM); + GtkTargetList *list = gtk_target_list_new (dest_drag_types, G_N_ELEMENTS (dest_drag_types)); + + GdkEvent *realevent = gtk_get_current_event(); + GdkEventMotion event; + event.type = GDK_MOTION_NOTIFY; + event.window = realevent->any.window; + event.send_event = FALSE; + event.axes = NULL; + event.time = gdk_event_get_time (realevent); + gdk_event_get_state (realevent, &event.state); + gdk_event_get_coords (realevent, &event.x, &event.y); + gdk_event_get_root_coords (realevent, &event.x_root, &event.y_root); + + gtk_drag_begin (toolitem, list, GDK_ACTION_MOVE, 1, (GdkEvent *)&event); + gtk_target_list_unref (list); +} + +static void +remove_item_cb (GtkAction *action, + EggEditableToolbar *etoolbar) +{ + GtkWidget *toolitem = gtk_widget_get_ancestor (egg_editable_toolbar_get_selected (etoolbar), GTK_TYPE_TOOL_ITEM); + GtkWidget *parent = gtk_widget_get_parent (toolitem); + int pos, toolbar_pos; + + toolbar_pos = get_toolbar_position (etoolbar, parent); + pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (parent), + GTK_TOOL_ITEM (toolitem)); + + egg_toolbars_model_remove_item (etoolbar->priv->model, + toolbar_pos, pos); + + if (egg_toolbars_model_n_items (etoolbar->priv->model, toolbar_pos) == 0) + { + egg_toolbars_model_remove_toolbar (etoolbar->priv->model, toolbar_pos); + } +} + +static void +remove_toolbar_cb (GtkAction *action, + EggEditableToolbar *etoolbar) +{ + GtkWidget *selected = egg_editable_toolbar_get_selected (etoolbar); + GtkWidget *toolbar = gtk_widget_get_ancestor (selected, GTK_TYPE_TOOLBAR); + int toolbar_pos; + + toolbar_pos = get_toolbar_position (etoolbar, toolbar); + egg_toolbars_model_remove_toolbar (etoolbar->priv->model, toolbar_pos); +} + +static void +popup_context_deactivate (GtkMenuShell *menu, + EggEditableToolbar *etoolbar) +{ + egg_editable_toolbar_set_selected (etoolbar, NULL); + g_object_notify (G_OBJECT (etoolbar), "selected"); +} + +static void +popup_context_menu_cb (GtkWidget *toolbar, + gint x, + gint y, + gint button_number, + EggEditableToolbar *etoolbar) +{ + if (etoolbar->priv->popup_path != NULL) + { + GtkMenu *menu; + + egg_editable_toolbar_set_selected (etoolbar, toolbar); + g_object_notify (G_OBJECT (etoolbar), "selected"); + + menu = GTK_MENU (gtk_ui_manager_get_widget (etoolbar->priv->manager, + etoolbar->priv->popup_path)); + g_return_if_fail (menu != NULL); + gtk_menu_popup (menu, NULL, NULL, NULL, NULL, button_number, gtk_get_current_event_time ()); + g_signal_connect_object (menu, "selection-done", + G_CALLBACK (popup_context_deactivate), + etoolbar, 0); + } +} + +static gboolean +button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + EggEditableToolbar *etoolbar) +{ + if (event->button == 3 && etoolbar->priv->popup_path != NULL) + { + GtkMenu *menu; + + egg_editable_toolbar_set_selected (etoolbar, widget); + g_object_notify (G_OBJECT (etoolbar), "selected"); + + menu = GTK_MENU (gtk_ui_manager_get_widget (etoolbar->priv->manager, + etoolbar->priv->popup_path)); + g_return_val_if_fail (menu != NULL, FALSE); + gtk_menu_popup (menu, NULL, NULL, NULL, NULL, event->button, event->time); + g_signal_connect_object (menu, "selection-done", + G_CALLBACK (popup_context_deactivate), + etoolbar, 0); + + return TRUE; + } + + return FALSE; +} + +static void +configure_item_sensitivity (GtkToolItem *item, EggEditableToolbar *etoolbar) +{ + GtkAction *action; + char *name; + + name = g_object_get_data (G_OBJECT (item), EGG_ITEM_NAME); + action = name ? find_action (etoolbar, name) : NULL; + + if (action) + { + g_object_notify (G_OBJECT (action), "sensitive"); + } + + gtk_tool_item_set_use_drag_window (item, + (etoolbar->priv->edit_mode > 0) || + GTK_IS_SEPARATOR_TOOL_ITEM (item)); + +} + +static void +configure_item_cursor (GtkToolItem *item, + EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + GtkWidget *widget = GTK_WIDGET (item); + GdkWindow *window = gtk_widget_get_window (widget); + + if (window != NULL) + { + if (priv->edit_mode > 0) + { + GdkCursor *cursor; + GdkScreen *screen; + GdkPixbuf *pixbuf = NULL; + + screen = gtk_widget_get_screen (GTK_WIDGET (etoolbar)); + + cursor = gdk_cursor_new_for_display (gdk_screen_get_display (screen), + GDK_HAND2); + gdk_window_set_cursor (window, cursor); + gdk_cursor_unref (cursor); + + gtk_drag_source_set (widget, GDK_BUTTON1_MASK, dest_drag_types, + G_N_ELEMENTS (dest_drag_types), GDK_ACTION_MOVE); + if (GTK_IS_SEPARATOR_TOOL_ITEM (item)) + { + pixbuf = new_separator_pixbuf (); + } + else + { + char *icon_name=NULL; + char *stock_id=NULL; + GtkAction *action; + char *name; + + name = g_object_get_data (G_OBJECT (widget), EGG_ITEM_NAME); + action = name ? find_action (etoolbar, name) : NULL; + + if (action) + { + g_object_get (action, + "icon-name", &icon_name, + "stock-id", &stock_id, + NULL); + } + if (icon_name) + { + GdkScreen *screen; + GtkIconTheme *icon_theme; + GtkSettings *settings; + gint width, height; + + screen = gtk_widget_get_screen (widget); + icon_theme = gtk_icon_theme_get_for_screen (screen); + settings = gtk_settings_get_for_screen (screen); + + if (!gtk_icon_size_lookup_for_settings (settings, + GTK_ICON_SIZE_LARGE_TOOLBAR, + &width, &height)) + { + width = height = 24; + } + + pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name, + MIN (width, height), 0, NULL); + } + else if (stock_id) + { + pixbuf = gtk_widget_render_icon (widget, stock_id, + GTK_ICON_SIZE_LARGE_TOOLBAR, NULL); + } + g_free (icon_name); + g_free (stock_id); + } + + if (G_UNLIKELY (!pixbuf)) + { + return; + } + gtk_drag_source_set_icon_pixbuf (widget, pixbuf); + g_object_unref (pixbuf); + + } + else + { + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET(item)), NULL); + } + } +} + + +static void +configure_item_tooltip (GtkToolItem *item) +{ + GtkAction *action; + +#if GTK_CHECK_VERSION (2, 16, 0) + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE (item)); +#else + action = gtk_widget_get_action (GTK_WIDGET (item)); +#endif + + if (action != NULL) + { + g_object_notify (G_OBJECT (action), "tooltip"); + } +} + + +static void +connect_widget_signals (GtkWidget *proxy, EggEditableToolbar *etoolbar) +{ + if (GTK_IS_CONTAINER (proxy)) + { + gtk_container_forall (GTK_CONTAINER (proxy), + (GtkCallback) connect_widget_signals, + (gpointer) etoolbar); + } + + if (GTK_IS_TOOL_ITEM (proxy)) + { + g_signal_connect_object (proxy, "drag_begin", + G_CALLBACK (drag_begin_cb), + etoolbar, 0); + g_signal_connect_object (proxy, "drag_end", + G_CALLBACK (drag_end_cb), + etoolbar, 0); + g_signal_connect_object (proxy, "drag_data_get", + G_CALLBACK (drag_data_get_cb), + etoolbar, 0); + g_signal_connect_object (proxy, "drag_data_delete", + G_CALLBACK (drag_data_delete_cb), + etoolbar, 0); + } + + if (GTK_IS_BUTTON (proxy) || GTK_IS_TOOL_ITEM (proxy)) + { + g_signal_connect_object (proxy, "button-press-event", + G_CALLBACK (button_press_event_cb), + etoolbar, 0); + } +} + +static void +action_sensitive_cb (GtkAction *action, + GParamSpec *pspec, + GtkToolItem *item) +{ + EggEditableToolbar *etoolbar = EGG_EDITABLE_TOOLBAR + (gtk_widget_get_ancestor (GTK_WIDGET (item), EGG_TYPE_EDITABLE_TOOLBAR)); + + if (etoolbar->priv->edit_mode > 0) + { + gtk_widget_set_sensitive (GTK_WIDGET (item), TRUE); + } +} + +static GtkToolItem * +create_item_from_action (EggEditableToolbar *etoolbar, + const char *name) +{ + GtkToolItem *item; + + g_return_val_if_fail (name != NULL, NULL); + + if (strcmp (name, "_separator") == 0) + { + item = gtk_separator_tool_item_new (); + } + else + { + GtkAction *action = find_action (etoolbar, name); + if (action == NULL) return NULL; + + item = GTK_TOOL_ITEM (gtk_action_create_tool_item (action)); + + /* Normally done on-demand by the GtkUIManager, but no + * such demand may have been made yet, so do it ourselves. + */ + gtk_action_set_accel_group + (action, gtk_ui_manager_get_accel_group(etoolbar->priv->manager)); + + g_signal_connect_object (action, "notify::sensitive", + G_CALLBACK (action_sensitive_cb), item, 0); + } + + gtk_widget_show (GTK_WIDGET (item)); + + g_object_set_data_full (G_OBJECT (item), EGG_ITEM_NAME, + g_strdup (name), g_free); + + return item; +} + +static GtkToolItem * +create_item_from_position (EggEditableToolbar *etoolbar, + int toolbar_position, + int position) +{ + GtkToolItem *item; + const char *name; + + name = egg_toolbars_model_item_nth (etoolbar->priv->model, toolbar_position, position); + item = create_item_from_action (etoolbar, name); + + return item; +} + +static void +toolbar_drag_data_received_cb (GtkToolbar *toolbar, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time, + EggEditableToolbar *etoolbar) +{ + /* This function can be called for two reasons + * + * (1) drag_motion() needs an item to pass to + * gtk_toolbar_set_drop_highlight_item(). We can + * recognize this case by etoolbar->priv->pending being TRUE + * We should just create an item and return. + * + * (2) The drag has finished, and drag_drop() wants us to + * actually add a new item to the toolbar. + */ + + GdkAtom type = gtk_selection_data_get_data_type (selection_data); + const char *data = (char *)gtk_selection_data_get_data (selection_data); + + int ipos = -1; + char *name = NULL; + gboolean used = FALSE; + + /* Find out where the drop is occuring, and the name of what is being dropped. */ + if (gtk_selection_data_get_length (selection_data) >= 0) + { + ipos = gtk_toolbar_get_drop_index (toolbar, x, y); + name = egg_toolbars_model_get_name (etoolbar->priv->model, type, data, FALSE); + if (name != NULL) + { + used = ((egg_toolbars_model_get_name_flags (etoolbar->priv->model, name) & EGG_TB_MODEL_NAME_USED) != 0); + } + } + + /* If we just want a highlight item, then . */ + if (etoolbar->priv->dnd_pending > 0) + { + etoolbar->priv->dnd_pending--; + + if (name != NULL && etoolbar->priv->dnd_toolbar == toolbar && !used) + { + etoolbar->priv->dnd_toolitem = create_item_from_action (etoolbar, name); + gtk_toolbar_set_drop_highlight_item (etoolbar->priv->dnd_toolbar, + etoolbar->priv->dnd_toolitem, ipos); + } + } + else + { + gtk_toolbar_set_drop_highlight_item (toolbar, NULL, 0); + etoolbar->priv->dnd_toolbar = NULL; + etoolbar->priv->dnd_toolitem = NULL; + + /* If we don't have a name to use yet, try to create one. */ + if (name == NULL && gtk_selection_data_get_length (selection_data) >= 0) + { + name = egg_toolbars_model_get_name (etoolbar->priv->model, type, data, TRUE); + } + + if (name != NULL && !used) + { + gint tpos = get_toolbar_position (etoolbar, GTK_WIDGET (toolbar)); + egg_toolbars_model_add_item (etoolbar->priv->model, tpos, ipos, name); + gtk_drag_finish (context, TRUE, + gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE, time); + } + else + { + gtk_drag_finish (context, FALSE, + gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE, time); + } + } + + g_free (name); +} + +static gboolean +toolbar_drag_drop_cb (GtkToolbar *toolbar, + GdkDragContext *context, + gint x, + gint y, + guint time, + EggEditableToolbar *etoolbar) +{ + GdkAtom target; + + target = gtk_drag_dest_find_target (GTK_WIDGET (toolbar), context, NULL); + if (target != GDK_NONE) + { + gtk_drag_get_data (GTK_WIDGET (toolbar), context, target, time); + return TRUE; + } + + return FALSE; +} + +static gboolean +toolbar_drag_motion_cb (GtkToolbar *toolbar, + GdkDragContext *context, + gint x, + gint y, + guint time, + EggEditableToolbar *etoolbar) +{ + GdkAtom target = gtk_drag_dest_find_target (GTK_WIDGET (toolbar), context, NULL); + if (target == GDK_NONE) + { + gdk_drag_status (context, 0, time); + return FALSE; + } + + /* Make ourselves the current dnd toolbar, and request a highlight item. */ + if (etoolbar->priv->dnd_toolbar != toolbar) + { + etoolbar->priv->dnd_toolbar = toolbar; + etoolbar->priv->dnd_toolitem = NULL; + etoolbar->priv->dnd_pending++; + gtk_drag_get_data (GTK_WIDGET (toolbar), context, target, time); + } + + /* If a highlight item is available, use it. */ + else if (etoolbar->priv->dnd_toolitem) + { + gint ipos = gtk_toolbar_get_drop_index (etoolbar->priv->dnd_toolbar, x, y); + gtk_toolbar_set_drop_highlight_item (etoolbar->priv->dnd_toolbar, + etoolbar->priv->dnd_toolitem, ipos); + } + + gdk_drag_status (context, gdk_drag_context_get_suggested_action (context), time); + + return TRUE; +} + +static void +toolbar_drag_leave_cb (GtkToolbar *toolbar, + GdkDragContext *context, + guint time, + EggEditableToolbar *etoolbar) +{ + gtk_toolbar_set_drop_highlight_item (toolbar, NULL, 0); + + /* If we were the current dnd toolbar target, remove the item. */ + if (etoolbar->priv->dnd_toolbar == toolbar) + { + etoolbar->priv->dnd_toolbar = NULL; + etoolbar->priv->dnd_toolitem = NULL; + } +} + +static void +configure_drag_dest (EggEditableToolbar *etoolbar, + GtkToolbar *toolbar) +{ + EggToolbarsItemType *type; + GtkTargetList *targets; + GList *list; + + /* Make every toolbar able to receive drag-drops. */ + gtk_drag_dest_set (GTK_WIDGET (toolbar), 0, + dest_drag_types, G_N_ELEMENTS (dest_drag_types), + GDK_ACTION_MOVE | GDK_ACTION_COPY); + + /* Add any specialist drag-drop abilities. */ + targets = gtk_drag_dest_get_target_list (GTK_WIDGET (toolbar)); + list = egg_toolbars_model_get_types (etoolbar->priv->model); + while (list) + { + type = list->data; + if (type->new_name != NULL || type->get_name != NULL) + gtk_target_list_add (targets, type->type, 0, 0); + list = list->next; + } +} + +static void +toggled_visibility_cb (GtkToggleAction *action, + EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + GtkWidget *dock; + EggTbModelFlags flags; + gboolean visible; + gint i; + + visible = gtk_toggle_action_get_active (action); + for (i = 0; i < priv->visibility_actions->len; i++) + if (g_ptr_array_index (priv->visibility_actions, i) == action) + break; + + g_return_if_fail (i < priv->visibility_actions->len); + + dock = get_dock_nth (etoolbar, i); + if (visible) + { + gtk_widget_show (dock); + } + else + { + gtk_widget_hide (dock); + } + + if (priv->save_hidden) + { + flags = egg_toolbars_model_get_flags (priv->model, i); + + if (visible) + { + flags &= ~(EGG_TB_MODEL_HIDDEN); + } + else + { + flags |= (EGG_TB_MODEL_HIDDEN); + } + + egg_toolbars_model_set_flags (priv->model, i, flags); + } +} + +static void +toolbar_visibility_refresh (EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + gint n_toolbars, n_items, i, j, k; + GtkToggleAction *action; + GList *list; + GString *string; + gboolean showing; + char action_name[40]; + char *action_label; + char *tmp; + + if (priv == NULL || priv->model == NULL || priv->manager == NULL || + priv->visibility_paths == NULL || priv->actions == NULL) + { + return; + } + + if (priv->visibility_actions == NULL) + { + priv->visibility_actions = g_ptr_array_new (); + } + + if (priv->visibility_id != 0) + { + gtk_ui_manager_remove_ui (priv->manager, priv->visibility_id); + } + + priv->visibility_id = gtk_ui_manager_new_merge_id (priv->manager); + +#if GTK_CHECK_VERSION(2,20,0) + showing = gtk_widget_get_visible (GTK_WIDGET (etoolbar)); +#else + showing = GTK_WIDGET_VISIBLE (etoolbar); +#endif + + n_toolbars = egg_toolbars_model_n_toolbars (priv->model); + for (i = 0; i < n_toolbars; i++) + { + string = g_string_sized_new (0); + n_items = egg_toolbars_model_n_items (priv->model, i); + for (k = 0, j = 0; j < n_items; j++) + { + GValue value = { 0, }; + GtkAction *action; + const char *name; + + name = egg_toolbars_model_item_nth (priv->model, i, j); + if (name == NULL) continue; + action = find_action (etoolbar, name); + if (action == NULL) continue; + + g_value_init (&value, G_TYPE_STRING); + g_object_get_property (G_OBJECT (action), "label", &value); + name = g_value_get_string (&value); + if (name == NULL) + { + g_value_unset (&value); + continue; + } + k += g_utf8_strlen (name, -1) + 2; + if (j > 0) + { + g_string_append (string, ", "); + if (j > 1 && k > 25) + { + g_value_unset (&value); + break; + } + } + g_string_append (string, name); + g_value_unset (&value); + } + if (j < n_items) + { + g_string_append (string, " ..."); + } + + tmp = g_string_free (string, FALSE); + for (j = 0, k = 0; tmp[j]; j++) + { + if (tmp[j] == '_') continue; + tmp[k] = tmp[j]; + k++; + } + tmp[k] = 0; + /* Translaters: This string is for a toggle to display a toolbar. + * The name of the toolbar is automatically computed from the widgets + * on the toolbar, and is placed at the %s. Note the _ before the %s + * which is used to add mnemonics. We know that this is likely to + * produce duplicates, but don't worry about it. If your language + * normally has a mnemonic at the start, please use the _. If not, + * please remove. */ + action_label = g_strdup_printf (_("Show ā€œ_%sā€"), tmp); + g_free (tmp); + + sprintf(action_name, "ToolbarToggle%d", i); + + if (i >= priv->visibility_actions->len) + { + action = gtk_toggle_action_new (action_name, action_label, NULL, NULL); + g_ptr_array_add (priv->visibility_actions, action); + g_signal_connect_object (action, "toggled", + G_CALLBACK (toggled_visibility_cb), + etoolbar, 0); + gtk_action_group_add_action (priv->actions, GTK_ACTION (action)); + } + else + { + action = g_ptr_array_index (priv->visibility_actions, i); + g_object_set (action, "label", action_label, NULL); + } + + gtk_action_set_visible (GTK_ACTION (action), (egg_toolbars_model_get_flags (priv->model, i) + & EGG_TB_MODEL_NOT_REMOVABLE) == 0); + gtk_action_set_sensitive (GTK_ACTION (action), showing); +#if GTK_CHECK_VERSION(2,20,0) + gtk_toggle_action_set_active (action, gtk_widget_get_visible + (get_dock_nth (etoolbar, i))); +#else + gtk_toggle_action_set_active (action, GTK_WIDGET_VISIBLE + (get_dock_nth (etoolbar, i))); +#endif + + for (list = priv->visibility_paths; list != NULL; list = g_list_next (list)) + { + gtk_ui_manager_add_ui (priv->manager, priv->visibility_id, + (const char *)list->data, action_name, action_name, + GTK_UI_MANAGER_MENUITEM, FALSE); + } + + g_free (action_label); + } + + gtk_ui_manager_ensure_update (priv->manager); + + while (i < priv->visibility_actions->len) + { + action = g_ptr_array_index (priv->visibility_actions, i); + g_ptr_array_remove_index_fast (priv->visibility_actions, i); + gtk_action_group_remove_action (priv->actions, GTK_ACTION (action)); + i++; + } +} + +static GtkWidget * +create_dock (EggEditableToolbar *etoolbar) +{ + GtkWidget *toolbar, *hbox; + + hbox = gtk_hbox_new (0, FALSE); + + toolbar = gtk_toolbar_new (); + gtk_toolbar_set_show_arrow (GTK_TOOLBAR (toolbar), TRUE); + gtk_widget_show (toolbar); + gtk_box_pack_start (GTK_BOX (hbox), toolbar, TRUE, TRUE, 0); + + g_signal_connect (toolbar, "drag_drop", + G_CALLBACK (toolbar_drag_drop_cb), etoolbar); + g_signal_connect (toolbar, "drag_motion", + G_CALLBACK (toolbar_drag_motion_cb), etoolbar); + g_signal_connect (toolbar, "drag_leave", + G_CALLBACK (toolbar_drag_leave_cb), etoolbar); + + g_signal_connect (toolbar, "drag_data_received", + G_CALLBACK (toolbar_drag_data_received_cb), etoolbar); + g_signal_connect (toolbar, "popup_context_menu", + G_CALLBACK (popup_context_menu_cb), etoolbar); + + configure_drag_dest (etoolbar, GTK_TOOLBAR (toolbar)); + + return hbox; +} + +static void +set_fixed_style (EggEditableToolbar *t, GtkToolbarStyle style) +{ + g_return_if_fail (GTK_IS_TOOLBAR (t->priv->fixed_toolbar)); + gtk_toolbar_set_style (GTK_TOOLBAR (t->priv->fixed_toolbar), + style == GTK_TOOLBAR_ICONS ? GTK_TOOLBAR_BOTH_HORIZ : style); +} + +static void +unset_fixed_style (EggEditableToolbar *t) +{ + g_return_if_fail (GTK_IS_TOOLBAR (t->priv->fixed_toolbar)); + gtk_toolbar_unset_style (GTK_TOOLBAR (t->priv->fixed_toolbar)); +} + +static void +toolbar_changed_cb (EggToolbarsModel *model, + int position, + EggEditableToolbar *etoolbar) +{ + GtkWidget *toolbar; + EggTbModelFlags flags; + GtkToolbarStyle style; + + flags = egg_toolbars_model_get_flags (model, position); + toolbar = get_toolbar_nth (etoolbar, position); + + if (flags & EGG_TB_MODEL_ICONS) + { + style = GTK_TOOLBAR_ICONS; + } + else if (flags & EGG_TB_MODEL_TEXT) + { + style = GTK_TOOLBAR_TEXT; + } + else if (flags & EGG_TB_MODEL_BOTH) + { + style = GTK_TOOLBAR_BOTH; + } + else if (flags & EGG_TB_MODEL_BOTH_HORIZ) + { + style = GTK_TOOLBAR_BOTH_HORIZ; + } + else + { + gtk_toolbar_unset_style (GTK_TOOLBAR (toolbar)); + if (position == 0 && etoolbar->priv->fixed_toolbar) + { + unset_fixed_style (etoolbar); + } + return; + } + + gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), style); + if (position == 0 && etoolbar->priv->fixed_toolbar) + { + set_fixed_style (etoolbar, style); + } + + toolbar_visibility_refresh (etoolbar); +} + +static void +unparent_fixed (EggEditableToolbar *etoolbar) +{ + GtkWidget *toolbar, *dock; + g_return_if_fail (GTK_IS_TOOLBAR (etoolbar->priv->fixed_toolbar)); + + toolbar = etoolbar->priv->fixed_toolbar; + dock = get_dock_nth (etoolbar, 0); + + if (dock && gtk_widget_get_parent (toolbar) != NULL) + { + gtk_container_remove (GTK_CONTAINER (dock), toolbar); + } +} + +static void +update_fixed (EggEditableToolbar *etoolbar) +{ + GtkWidget *toolbar, *dock; + if (!etoolbar->priv->fixed_toolbar) return; + + toolbar = etoolbar->priv->fixed_toolbar; + dock = get_dock_nth (etoolbar, 0); + + if (dock && toolbar && gtk_widget_get_parent (toolbar) == NULL) + { + gtk_box_pack_end (GTK_BOX (dock), toolbar, FALSE, TRUE, 0); + + gtk_widget_show (toolbar); + + gtk_widget_set_size_request (dock, -1, -1); + gtk_widget_queue_resize_no_redraw (dock); + } +} + +static void +toolbar_added_cb (EggToolbarsModel *model, + int position, + EggEditableToolbar *etoolbar) +{ + GtkWidget *dock; + + dock = create_dock (etoolbar); + if ((egg_toolbars_model_get_flags (model, position) & EGG_TB_MODEL_HIDDEN) == 0) + gtk_widget_show (dock); + + gtk_widget_set_size_request (dock, -1, MIN_TOOLBAR_HEIGHT); + + gtk_box_pack_start (GTK_BOX (etoolbar), dock, TRUE, TRUE, 0); + + gtk_box_reorder_child (GTK_BOX (etoolbar), dock, position); + + gtk_widget_show_all (dock); + + update_fixed (etoolbar); + + toolbar_visibility_refresh (etoolbar); +} + +static void +toolbar_removed_cb (EggToolbarsModel *model, + int position, + EggEditableToolbar *etoolbar) +{ + GtkWidget *dock; + + if (position == 0 && etoolbar->priv->fixed_toolbar != NULL) + { + unparent_fixed (etoolbar); + } + + dock = get_dock_nth (etoolbar, position); + gtk_widget_destroy (dock); + + update_fixed (etoolbar); + + toolbar_visibility_refresh (etoolbar); +} + +static void +item_added_cb (EggToolbarsModel *model, + int tpos, + int ipos, + EggEditableToolbar *etoolbar) +{ + GtkWidget *dock; + GtkWidget *toolbar; + GtkToolItem *item; + + toolbar = get_toolbar_nth (etoolbar, tpos); + item = create_item_from_position (etoolbar, tpos, ipos); + if (item == NULL) return; + + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, ipos); + + connect_widget_signals (GTK_WIDGET (item), etoolbar); + configure_item_tooltip (item); + configure_item_cursor (item, etoolbar); + configure_item_sensitivity (item, etoolbar); + + dock = get_dock_nth (etoolbar, tpos); + gtk_widget_set_size_request (dock, -1, -1); + gtk_widget_queue_resize_no_redraw (dock); + + toolbar_visibility_refresh (etoolbar); +} + +static void +item_removed_cb (EggToolbarsModel *model, + int toolbar_position, + int position, + EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + + GtkWidget *toolbar; + GtkWidget *item; + + toolbar = get_toolbar_nth (etoolbar, toolbar_position); + item = GTK_WIDGET (gtk_toolbar_get_nth_item + (GTK_TOOLBAR (toolbar), position)); + g_return_if_fail (item != NULL); + + if (item == priv->selected) + { + /* FIXME */ + } + + gtk_container_remove (GTK_CONTAINER (toolbar), item); + + toolbar_visibility_refresh (etoolbar); +} + +static void +egg_editable_toolbar_build (EggEditableToolbar *etoolbar) +{ + int i, l, n_items, n_toolbars; + EggToolbarsModel *model = etoolbar->priv->model; + + g_return_if_fail (model != NULL); + g_return_if_fail (etoolbar->priv->manager != NULL); + + n_toolbars = egg_toolbars_model_n_toolbars (model); + + for (i = 0; i < n_toolbars; i++) + { + GtkWidget *toolbar, *dock; + + dock = create_dock (etoolbar); + if ((egg_toolbars_model_get_flags (model, i) & EGG_TB_MODEL_HIDDEN) == 0) + gtk_widget_show (dock); + gtk_box_pack_start (GTK_BOX (etoolbar), dock, TRUE, TRUE, 0); + toolbar = get_toolbar_nth (etoolbar, i); + + n_items = egg_toolbars_model_n_items (model, i); + for (l = 0; l < n_items; l++) + { + GtkToolItem *item; + + item = create_item_from_position (etoolbar, i, l); + if (item) + { + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, l); + + connect_widget_signals (GTK_WIDGET (item), etoolbar); + configure_item_tooltip (item); + configure_item_sensitivity (item, etoolbar); + } + else + { + egg_toolbars_model_remove_item (model, i, l); + l--; + n_items--; + } + } + + if (n_items == 0) + { + gtk_widget_set_size_request (dock, -1, MIN_TOOLBAR_HEIGHT); + } + } + + update_fixed (etoolbar); + + /* apply styles */ + for (i = 0; i < n_toolbars; i ++) + { + toolbar_changed_cb (model, i, etoolbar); + } +} + +static void +egg_editable_toolbar_disconnect_model (EggEditableToolbar *toolbar) +{ + EggToolbarsModel *model = toolbar->priv->model; + + g_signal_handlers_disconnect_by_func + (model, G_CALLBACK (item_added_cb), toolbar); + g_signal_handlers_disconnect_by_func + (model, G_CALLBACK (item_removed_cb), toolbar); + g_signal_handlers_disconnect_by_func + (model, G_CALLBACK (toolbar_added_cb), toolbar); + g_signal_handlers_disconnect_by_func + (model, G_CALLBACK (toolbar_removed_cb), toolbar); + g_signal_handlers_disconnect_by_func + (model, G_CALLBACK (toolbar_changed_cb), toolbar); +} + +static void +egg_editable_toolbar_deconstruct (EggEditableToolbar *toolbar) +{ + EggToolbarsModel *model = toolbar->priv->model; + GList *children; + + g_return_if_fail (model != NULL); + + if (toolbar->priv->fixed_toolbar) + { + unset_fixed_style (toolbar); + unparent_fixed (toolbar); + } + + children = gtk_container_get_children (GTK_CONTAINER (toolbar)); + g_list_foreach (children, (GFunc) gtk_widget_destroy, NULL); + g_list_free (children); +} + +void +egg_editable_toolbar_set_model (EggEditableToolbar *etoolbar, + EggToolbarsModel *model) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + + if (priv->model == model) return; + + if (priv->model) + { + egg_editable_toolbar_disconnect_model (etoolbar); + egg_editable_toolbar_deconstruct (etoolbar); + + g_object_unref (priv->model); + } + + priv->model = g_object_ref (model); + + egg_editable_toolbar_build (etoolbar); + + toolbar_visibility_refresh (etoolbar); + + g_signal_connect (model, "item_added", + G_CALLBACK (item_added_cb), etoolbar); + g_signal_connect (model, "item_removed", + G_CALLBACK (item_removed_cb), etoolbar); + g_signal_connect (model, "toolbar_added", + G_CALLBACK (toolbar_added_cb), etoolbar); + g_signal_connect (model, "toolbar_removed", + G_CALLBACK (toolbar_removed_cb), etoolbar); + g_signal_connect (model, "toolbar_changed", + G_CALLBACK (toolbar_changed_cb), etoolbar); +} + +static void +egg_editable_toolbar_init (EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv; + + priv = etoolbar->priv = EGG_EDITABLE_TOOLBAR_GET_PRIVATE (etoolbar); + + priv->save_hidden = TRUE; + + g_signal_connect (etoolbar, "notify::visible", + G_CALLBACK (toolbar_visibility_refresh), NULL); +} + +static void +egg_editable_toolbar_dispose (GObject *object) +{ + EggEditableToolbar *etoolbar = EGG_EDITABLE_TOOLBAR (object); + EggEditableToolbarPrivate *priv = etoolbar->priv; + GList *children; + + if (priv->fixed_toolbar != NULL) + { + g_object_unref (priv->fixed_toolbar); + priv->fixed_toolbar = NULL; + } + + if (priv->visibility_paths) + { + children = priv->visibility_paths; + g_list_foreach (children, (GFunc) g_free, NULL); + g_list_free (children); + priv->visibility_paths = NULL; + } + + g_free (priv->popup_path); + priv->popup_path = NULL; + + if (priv->manager != NULL) + { + if (priv->visibility_id) + { + gtk_ui_manager_remove_ui (priv->manager, priv->visibility_id); + priv->visibility_id = 0; + } + + g_object_unref (priv->manager); + priv->manager = NULL; + } + + if (priv->model) + { + egg_editable_toolbar_disconnect_model (etoolbar); + g_object_unref (priv->model); + priv->model = NULL; + } + + G_OBJECT_CLASS (egg_editable_toolbar_parent_class)->dispose (object); +} + +static void +egg_editable_toolbar_set_ui_manager (EggEditableToolbar *etoolbar, + GtkUIManager *manager) +{ + static const GtkActionEntry actions[] = { + { "MoveToolItem", STOCK_DRAG_MODE, N_("_Move on Toolbar"), NULL, + N_("Move the selected item on the toolbar"), G_CALLBACK (move_item_cb) }, + { "RemoveToolItem", GTK_STOCK_REMOVE, N_("_Remove from Toolbar"), NULL, + N_("Remove the selected item from the toolbar"), G_CALLBACK (remove_item_cb) }, + { "RemoveToolbar", GTK_STOCK_DELETE, N_("_Delete Toolbar"), NULL, + N_("Remove the selected toolbar"), G_CALLBACK (remove_toolbar_cb) }, + }; + + etoolbar->priv->manager = g_object_ref (manager); + + etoolbar->priv->actions = gtk_action_group_new ("ToolbarActions"); + gtk_action_group_set_translation_domain (etoolbar->priv->actions, GETTEXT_PACKAGE); + gtk_action_group_add_actions (etoolbar->priv->actions, actions, + G_N_ELEMENTS (actions), etoolbar); + gtk_ui_manager_insert_action_group (manager, etoolbar->priv->actions, -1); + g_object_unref (etoolbar->priv->actions); + + toolbar_visibility_refresh (etoolbar); +} + +GtkWidget * egg_editable_toolbar_get_selected (EggEditableToolbar *etoolbar) +{ + return etoolbar->priv->selected; +} + +void +egg_editable_toolbar_set_selected (EggEditableToolbar *etoolbar, + GtkWidget *widget) +{ + GtkWidget *toolbar, *toolitem; + gboolean editable; + + etoolbar->priv->selected = widget; + + toolbar = (widget != NULL) ? gtk_widget_get_ancestor (widget, GTK_TYPE_TOOLBAR) : NULL; + toolitem = (widget != NULL) ? gtk_widget_get_ancestor (widget, GTK_TYPE_TOOL_ITEM) : NULL; + + if(toolbar != NULL) + { + gint tpos = get_toolbar_position (etoolbar, toolbar); + editable = ((egg_toolbars_model_get_flags (etoolbar->priv->model, tpos) & EGG_TB_MODEL_NOT_EDITABLE) == 0); + } + else + { + editable = FALSE; + } + + gtk_action_set_visible (find_action (etoolbar, "RemoveToolbar"), (toolbar != NULL) && (etoolbar->priv->edit_mode > 0)); + gtk_action_set_visible (find_action (etoolbar, "RemoveToolItem"), (toolitem != NULL) && editable); + gtk_action_set_visible (find_action (etoolbar, "MoveToolItem"), (toolitem != NULL) && editable); +} + +static void +set_edit_mode (EggEditableToolbar *etoolbar, + gboolean mode) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + int i, l, n_items; + + i = priv->edit_mode; + if (mode) + { + priv->edit_mode++; + } + else + { + g_return_if_fail (priv->edit_mode > 0); + priv->edit_mode--; + } + i *= priv->edit_mode; + + if (i == 0) + { + for (i = get_n_toolbars (etoolbar)-1; i >= 0; i--) + { + GtkWidget *toolbar; + + toolbar = get_toolbar_nth (etoolbar, i); + n_items = gtk_toolbar_get_n_items (GTK_TOOLBAR (toolbar)); + + if (n_items == 0 && priv->edit_mode == 0) + { + egg_toolbars_model_remove_toolbar (priv->model, i); + } + else + { + for (l = 0; l < n_items; l++) + { + GtkToolItem *item; + + item = gtk_toolbar_get_nth_item (GTK_TOOLBAR (toolbar), l); + + configure_item_cursor (item, etoolbar); + configure_item_sensitivity (item, etoolbar); + } + } + } + } +} + +static void +egg_editable_toolbar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggEditableToolbar *etoolbar = EGG_EDITABLE_TOOLBAR (object); + + switch (prop_id) + { + case PROP_UI_MANAGER: + egg_editable_toolbar_set_ui_manager (etoolbar, g_value_get_object (value)); + break; + case PROP_TOOLBARS_MODEL: + egg_editable_toolbar_set_model (etoolbar, g_value_get_object (value)); + break; + case PROP_SELECTED: + egg_editable_toolbar_set_selected (etoolbar, g_value_get_object (value)); + break; + case PROP_POPUP_PATH: + etoolbar->priv->popup_path = g_strdup (g_value_get_string (value)); + break; + case PROP_EDIT_MODE: + set_edit_mode (etoolbar, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_editable_toolbar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggEditableToolbar *etoolbar = EGG_EDITABLE_TOOLBAR (object); + + switch (prop_id) + { + case PROP_UI_MANAGER: + g_value_set_object (value, etoolbar->priv->manager); + break; + case PROP_TOOLBARS_MODEL: + g_value_set_object (value, etoolbar->priv->model); + break; + case PROP_SELECTED: + g_value_set_object (value, etoolbar->priv->selected); + break; + case PROP_EDIT_MODE: + g_value_set_boolean (value, etoolbar->priv->edit_mode>0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_editable_toolbar_class_init (EggEditableToolbarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = egg_editable_toolbar_dispose; + object_class->set_property = egg_editable_toolbar_set_property; + object_class->get_property = egg_editable_toolbar_get_property; + + egg_editable_toolbar_signals[ACTION_REQUEST] = + g_signal_new ("action_request", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggEditableToolbarClass, action_request), + NULL, NULL, g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + g_object_class_install_property (object_class, + PROP_UI_MANAGER, + g_param_spec_object ("ui-manager", + "UI-Mmanager", + "UI Manager", + GTK_TYPE_UI_MANAGER, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + g_object_class_install_property (object_class, + PROP_TOOLBARS_MODEL, + g_param_spec_object ("model", + "Model", + "Toolbars Model", + EGG_TYPE_TOOLBARS_MODEL, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + g_object_class_install_property (object_class, + PROP_SELECTED, + g_param_spec_object ("selected", + "Selected", + "Selected toolitem", + GTK_TYPE_TOOL_ITEM, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (object_class, + PROP_POPUP_PATH, + g_param_spec_string ("popup-path", + "popup-path", + "popup-path", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (object_class, + PROP_EDIT_MODE, + g_param_spec_boolean ("edit-mode", + "Edit-Mode", + "Edit Mode", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (object_class, sizeof (EggEditableToolbarPrivate)); +} + +GtkWidget * +egg_editable_toolbar_new (GtkUIManager *manager, + const char *popup_path) +{ + return GTK_WIDGET (g_object_new (EGG_TYPE_EDITABLE_TOOLBAR, + "ui-manager", manager, + "popup-path", popup_path, + NULL)); +} + +GtkWidget * +egg_editable_toolbar_new_with_model (GtkUIManager *manager, + EggToolbarsModel *model, + const char *popup_path) +{ + return GTK_WIDGET (g_object_new (EGG_TYPE_EDITABLE_TOOLBAR, + "ui-manager", manager, + "model", model, + "popup-path", popup_path, + NULL)); +} + +gboolean +egg_editable_toolbar_get_edit_mode (EggEditableToolbar *etoolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + + return priv->edit_mode > 0; +} + +void +egg_editable_toolbar_set_edit_mode (EggEditableToolbar *etoolbar, + gboolean mode) +{ + set_edit_mode (etoolbar, mode); + g_object_notify (G_OBJECT (etoolbar), "edit-mode"); +} + +void +egg_editable_toolbar_add_visibility (EggEditableToolbar *etoolbar, + const char *path) +{ + etoolbar->priv->visibility_paths = g_list_prepend + (etoolbar->priv->visibility_paths, g_strdup (path)); +} + +void +egg_editable_toolbar_show (EggEditableToolbar *etoolbar, + const char *name) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + EggToolbarsModel *model = priv->model; + int i, n_toolbars; + + n_toolbars = egg_toolbars_model_n_toolbars (model); + for (i = 0; i < n_toolbars; i++) + { + const char *toolbar_name; + + toolbar_name = egg_toolbars_model_toolbar_nth (model, i); + if (strcmp (toolbar_name, name) == 0) + { + gtk_widget_show (get_dock_nth (etoolbar, i)); + } + } +} + +void +egg_editable_toolbar_hide (EggEditableToolbar *etoolbar, + const char *name) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + EggToolbarsModel *model = priv->model; + int i, n_toolbars; + + n_toolbars = egg_toolbars_model_n_toolbars (model); + for (i = 0; i < n_toolbars; i++) + { + const char *toolbar_name; + + toolbar_name = egg_toolbars_model_toolbar_nth (model, i); + if (strcmp (toolbar_name, name) == 0) + { + gtk_widget_hide (get_dock_nth (etoolbar, i)); + } + } +} + +void +egg_editable_toolbar_set_fixed (EggEditableToolbar *etoolbar, + GtkToolbar *toolbar) +{ + EggEditableToolbarPrivate *priv = etoolbar->priv; + + g_return_if_fail (!toolbar || GTK_IS_TOOLBAR (toolbar)); + + if (priv->fixed_toolbar) + { + unparent_fixed (etoolbar); + g_object_unref (priv->fixed_toolbar); + priv->fixed_toolbar = NULL; + } + + if (toolbar) + { + priv->fixed_toolbar = GTK_WIDGET (toolbar); + gtk_toolbar_set_show_arrow (toolbar, FALSE); + g_object_ref_sink (toolbar); + } + + update_fixed (etoolbar); +} + +#define DEFAULT_ICON_HEIGHT 20 + +/* We should probably experiment some more with this. + * Right now the rendered icon is pretty good for most + * themes. However, the icon is slightly large for themes + * with large toolbar icons. + */ +static GdkPixbuf * +new_pixbuf_from_widget (GtkWidget *widget) +{ + GtkWidget *window; + GdkPixbuf *pixbuf; + gint icon_height; + GdkScreen *screen; + + screen = gtk_widget_get_screen (widget); + + if (!gtk_icon_size_lookup_for_settings (gtk_settings_get_for_screen (screen), + GTK_ICON_SIZE_LARGE_TOOLBAR, + NULL, + &icon_height)) + { + icon_height = DEFAULT_ICON_HEIGHT; + } + + window = gtk_offscreen_window_new (); + /* Set the width to -1 as we want the separator to be as thin as possible. */ + gtk_widget_set_size_request (widget, -1, icon_height); + + gtk_container_add (GTK_CONTAINER (window), widget); + gtk_widget_show_all (window); + + /* Process the waiting events to have the widget actually drawn */ + gdk_window_process_updates (gtk_widget_get_window (window), TRUE); + pixbuf = gtk_offscreen_window_get_pixbuf (GTK_OFFSCREEN_WINDOW (window)); + gtk_widget_destroy (window); + + return pixbuf; +} + +static GdkPixbuf * +new_separator_pixbuf (void) +{ + GtkWidget *separator; + GdkPixbuf *pixbuf; + + separator = gtk_vseparator_new (); + pixbuf = new_pixbuf_from_widget (separator); + return pixbuf; +} + +static void +update_separator_image (GtkImage *image) +{ + GdkPixbuf *pixbuf = new_separator_pixbuf (); + gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); + g_object_unref (pixbuf); +} + +static gboolean +style_set_cb (GtkWidget *widget, + GtkStyle *previous_style, + GtkImage *image) +{ + + update_separator_image (image); + return FALSE; +} + +GtkWidget * +_egg_editable_toolbar_new_separator_image (void) +{ + GtkWidget *image = gtk_image_new (); + update_separator_image (GTK_IMAGE (image)); + g_signal_connect (G_OBJECT (image), "style_set", + G_CALLBACK (style_set_cb), GTK_IMAGE (image)); + + return image; +} + +EggToolbarsModel * +egg_editable_toolbar_get_model (EggEditableToolbar *etoolbar) +{ + return etoolbar->priv->model; +} diff --git a/cut-n-paste/toolbar-editor/egg-editable-toolbar.h b/cut-n-paste/toolbar-editor/egg-editable-toolbar.h new file mode 100644 index 00000000..669af415 --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-editable-toolbar.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003, 2004 Marco Pesenti Gritti + * Copyright (C) 2003, 2004, 2005 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#ifndef EGG_EDITABLE_TOOLBAR_H +#define EGG_EDITABLE_TOOLBAR_H + +#include "egg-toolbars-model.h" + +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_EDITABLE_TOOLBAR (egg_editable_toolbar_get_type ()) +#define EGG_EDITABLE_TOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_EDITABLE_TOOLBAR, EggEditableToolbar)) +#define EGG_EDITABLE_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_EDITABLE_TOOLBAR, EggEditableToolbarClass)) +#define EGG_IS_EDITABLE_TOOLBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_EDITABLE_TOOLBAR)) +#define EGG_IS_EDITABLE_TOOLBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_EDITABLE_TOOLBAR)) +#define EGG_EDITABLE_TOOLBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_EDITABLE_TOOLBAR, EggEditableToolbarClass)) + +typedef struct _EggEditableToolbar EggEditableToolbar; +typedef struct _EggEditableToolbarPrivate EggEditableToolbarPrivate; +typedef struct _EggEditableToolbarClass EggEditableToolbarClass; + +struct _EggEditableToolbar +{ + GtkVBox parent_object; + + /*< private >*/ + EggEditableToolbarPrivate *priv; +}; + +struct _EggEditableToolbarClass +{ + GtkVBoxClass parent_class; + + void (* action_request) (EggEditableToolbar *etoolbar, + const char *action_name); +}; + +GType egg_editable_toolbar_get_type (void); +GtkWidget *egg_editable_toolbar_new (GtkUIManager *manager, + const char *visibility_path); +GtkWidget *egg_editable_toolbar_new_with_model (GtkUIManager *manager, + EggToolbarsModel *model, + const char *visibility_path); +void egg_editable_toolbar_set_model (EggEditableToolbar *etoolbar, + EggToolbarsModel *model); +EggToolbarsModel *egg_editable_toolbar_get_model (EggEditableToolbar *etoolbar); +GtkUIManager *egg_editable_toolbar_get_manager (EggEditableToolbar *etoolbar); +void egg_editable_toolbar_set_edit_mode (EggEditableToolbar *etoolbar, + gboolean mode); +gboolean egg_editable_toolbar_get_edit_mode (EggEditableToolbar *etoolbar); +void egg_editable_toolbar_show (EggEditableToolbar *etoolbar, + const char *name); +void egg_editable_toolbar_hide (EggEditableToolbar *etoolbar, + const char *name); +void egg_editable_toolbar_set_fixed (EggEditableToolbar *etoolbar, + GtkToolbar *fixed_toolbar); + +GtkWidget * egg_editable_toolbar_get_selected (EggEditableToolbar *etoolbar); +void egg_editable_toolbar_set_selected (EggEditableToolbar *etoolbar, + GtkWidget *widget); + +void egg_editable_toolbar_add_visibility (EggEditableToolbar *etoolbar, + const char *path); + +/* Private Functions */ + +GtkWidget *_egg_editable_toolbar_new_separator_image (void); + +G_END_DECLS + +#endif diff --git a/cut-n-paste/toolbar-editor/egg-toolbar-editor.c b/cut-n-paste/toolbar-editor/egg-toolbar-editor.c new file mode 100644 index 00000000..5e75ba82 --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-toolbar-editor.c @@ -0,0 +1,672 @@ +/* + * Copyright (C) 2003 Marco Pesenti Gritti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "egg-toolbar-editor.h" +#include "egg-editable-toolbar.h" + +#include +#include +#include +#include + +static const GtkTargetEntry dest_drag_types[] = { + {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0}, +}; + +static const GtkTargetEntry source_drag_types[] = { + {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0}, +}; + + +static void egg_toolbar_editor_finalize (GObject *object); +static void update_editor_sheet (EggToolbarEditor *editor); + +enum +{ + PROP_0, + PROP_UI_MANAGER, + PROP_TOOLBARS_MODEL +}; + +enum +{ + SIGNAL_HANDLER_ITEM_ADDED, + SIGNAL_HANDLER_ITEM_REMOVED, + SIGNAL_HANDLER_TOOLBAR_REMOVED, + SIGNAL_HANDLER_LIST_SIZE /* Array size */ +}; + +#define EGG_TOOLBAR_EDITOR_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EGG_TYPE_TOOLBAR_EDITOR, EggToolbarEditorPrivate)) + +struct EggToolbarEditorPrivate +{ + GtkUIManager *manager; + EggToolbarsModel *model; + + GtkWidget *table; + GtkWidget *scrolled_window; + GList *actions_list; + GList *factory_list; + + /* These handlers need to be sanely disconnected when switching models */ + gulong sig_handlers[SIGNAL_HANDLER_LIST_SIZE]; +}; + +G_DEFINE_TYPE (EggToolbarEditor, egg_toolbar_editor, GTK_TYPE_VBOX); + +static gint +compare_items (gconstpointer a, + gconstpointer b) +{ + const GtkWidget *item1 = a; + const GtkWidget *item2 = b; + + char *key1 = g_object_get_data (G_OBJECT (item1), + "egg-collate-key"); + char *key2 = g_object_get_data (G_OBJECT (item2), + "egg-collate-key"); + + return strcmp (key1, key2); +} + +static GtkAction * +find_action (EggToolbarEditor *t, + const char *name) +{ + GList *l; + GtkAction *action = NULL; + + l = gtk_ui_manager_get_action_groups (t->priv->manager); + + g_return_val_if_fail (EGG_IS_TOOLBAR_EDITOR (t), NULL); + g_return_val_if_fail (name != NULL, NULL); + + for (; l != NULL; l = l->next) + { + GtkAction *tmp; + + tmp = gtk_action_group_get_action (GTK_ACTION_GROUP (l->data), name); + if (tmp) + action = tmp; + } + + return action; +} + +static void +egg_toolbar_editor_set_ui_manager (EggToolbarEditor *t, + GtkUIManager *manager) +{ + g_return_if_fail (GTK_IS_UI_MANAGER (manager)); + + t->priv->manager = g_object_ref (manager); +} + +static void +item_added_or_removed_cb (EggToolbarsModel *model, + int tpos, + int ipos, + EggToolbarEditor *editor) +{ + update_editor_sheet (editor); +} + +static void +toolbar_removed_cb (EggToolbarsModel *model, + int position, + EggToolbarEditor *editor) +{ + update_editor_sheet (editor); +} + +static void +egg_toolbar_editor_disconnect_model (EggToolbarEditor *t) +{ + EggToolbarEditorPrivate *priv = t->priv; + EggToolbarsModel *model = priv->model; + gulong handler; + int i; + + for (i = 0; i < SIGNAL_HANDLER_LIST_SIZE; i++) + { + handler = priv->sig_handlers[i]; + + if (handler != 0) + { + if (g_signal_handler_is_connected (model, handler)) + { + g_signal_handler_disconnect (model, handler); + } + + priv->sig_handlers[i] = 0; + } + } +} + +void +egg_toolbar_editor_set_model (EggToolbarEditor *t, + EggToolbarsModel *model) +{ + EggToolbarEditorPrivate *priv; + + g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (t)); + g_return_if_fail (model != NULL); + + priv = t->priv; + + if (priv->model) + { + if (G_UNLIKELY (priv->model == model)) return; + + egg_toolbar_editor_disconnect_model (t); + g_object_unref (priv->model); + } + + priv->model = g_object_ref (model); + + update_editor_sheet (t); + + priv->sig_handlers[SIGNAL_HANDLER_ITEM_ADDED] = + g_signal_connect_object (model, "item_added", + G_CALLBACK (item_added_or_removed_cb), t, 0); + priv->sig_handlers[SIGNAL_HANDLER_ITEM_REMOVED] = + g_signal_connect_object (model, "item_removed", + G_CALLBACK (item_added_or_removed_cb), t, 0); + priv->sig_handlers[SIGNAL_HANDLER_TOOLBAR_REMOVED] = + g_signal_connect_object (model, "toolbar_removed", + G_CALLBACK (toolbar_removed_cb), t, 0); +} + +static void +egg_toolbar_editor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggToolbarEditor *t = EGG_TOOLBAR_EDITOR (object); + + switch (prop_id) + { + case PROP_UI_MANAGER: + egg_toolbar_editor_set_ui_manager (t, g_value_get_object (value)); + break; + case PROP_TOOLBARS_MODEL: + egg_toolbar_editor_set_model (t, g_value_get_object (value)); + break; + } +} + +static void +egg_toolbar_editor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggToolbarEditor *t = EGG_TOOLBAR_EDITOR (object); + + switch (prop_id) + { + case PROP_UI_MANAGER: + g_value_set_object (value, t->priv->manager); + break; + case PROP_TOOLBARS_MODEL: + g_value_set_object (value, t->priv->model); + break; + } +} + +static void +egg_toolbar_editor_class_init (EggToolbarEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = egg_toolbar_editor_finalize; + object_class->set_property = egg_toolbar_editor_set_property; + object_class->get_property = egg_toolbar_editor_get_property; + + g_object_class_install_property (object_class, + PROP_UI_MANAGER, + g_param_spec_object ("ui-manager", + "UI-Manager", + "UI Manager", + GTK_TYPE_UI_MANAGER, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_TOOLBARS_MODEL, + g_param_spec_object ("model", + "Model", + "Toolbars Model", + EGG_TYPE_TOOLBARS_MODEL, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | + G_PARAM_CONSTRUCT)); + + g_type_class_add_private (object_class, sizeof (EggToolbarEditorPrivate)); +} + +static void +egg_toolbar_editor_finalize (GObject *object) +{ + EggToolbarEditor *editor = EGG_TOOLBAR_EDITOR (object); + + if (editor->priv->manager) + { + g_object_unref (editor->priv->manager); + } + + if (editor->priv->model) + { + egg_toolbar_editor_disconnect_model (editor); + g_object_unref (editor->priv->model); + } + + g_list_free (editor->priv->actions_list); + g_list_free (editor->priv->factory_list); + + G_OBJECT_CLASS (egg_toolbar_editor_parent_class)->finalize (object); +} + +GtkWidget * +egg_toolbar_editor_new (GtkUIManager *manager, + EggToolbarsModel *model) +{ + return GTK_WIDGET (g_object_new (EGG_TYPE_TOOLBAR_EDITOR, + "ui-manager", manager, + "model", model, + NULL)); +} + +static void +drag_begin_cb (GtkWidget *widget, + GdkDragContext *context) +{ + gtk_widget_hide (widget); +} + +static void +drag_end_cb (GtkWidget *widget, + GdkDragContext *context) +{ + gtk_widget_show (widget); +} + +static void +drag_data_get_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint32 time, + EggToolbarEditor *editor) +{ + const char *target; + + target = g_object_get_data (G_OBJECT (widget), "egg-item-name"); + g_return_if_fail (target != NULL); + + gtk_selection_data_set (selection_data, + gtk_selection_data_get_target (selection_data), 8, + (const guchar *) target, strlen (target)); +} + +static gchar * +elide_underscores (const gchar *original) +{ + gchar *q, *result; + const gchar *p; + gboolean last_underscore; + + q = result = g_malloc (strlen (original) + 1); + last_underscore = FALSE; + + for (p = original; *p; p++) + { + if (!last_underscore && *p == '_') + last_underscore = TRUE; + else + { + last_underscore = FALSE; + *q++ = *p; + } + } + + *q = '\0'; + + return result; +} + +static void +set_drag_cursor (GtkWidget *widget) +{ + GdkCursor *cursor; + GdkScreen *screen; + + screen = gtk_widget_get_screen (widget); + + cursor = gdk_cursor_new_for_display (gdk_screen_get_display (screen), + GDK_HAND2); + gdk_window_set_cursor (gtk_widget_get_window (widget), cursor); + gdk_cursor_unref (cursor); +} + +static void +event_box_realize_cb (GtkWidget *widget, GtkImage *icon) +{ + GtkImageType type; + + set_drag_cursor (widget); + + type = gtk_image_get_storage_type (icon); + if (type == GTK_IMAGE_STOCK) + { + gchar *stock_id; + GdkPixbuf *pixbuf; + + gtk_image_get_stock (icon, &stock_id, NULL); + pixbuf = gtk_widget_render_icon (widget, stock_id, + GTK_ICON_SIZE_LARGE_TOOLBAR, NULL); + gtk_drag_source_set_icon_pixbuf (widget, pixbuf); + g_object_unref (pixbuf); + } + else if (type == GTK_IMAGE_ICON_NAME) + { + const gchar *icon_name; + GdkScreen *screen; + GtkIconTheme *icon_theme; + GtkSettings *settings; + gint width, height; + GdkPixbuf *pixbuf; + + gtk_image_get_icon_name (icon, &icon_name, NULL); + screen = gtk_widget_get_screen (widget); + icon_theme = gtk_icon_theme_get_for_screen (screen); + settings = gtk_settings_get_for_screen (screen); + + if (!gtk_icon_size_lookup_for_settings (settings, + GTK_ICON_SIZE_LARGE_TOOLBAR, + &width, &height)) + { + width = height = 24; + } + + pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name, + MIN (width, height), 0, NULL); + if (G_UNLIKELY (!pixbuf)) + return; + + gtk_drag_source_set_icon_pixbuf (widget, pixbuf); + g_object_unref (pixbuf); + + } + else if (type == GTK_IMAGE_PIXBUF) + { + GdkPixbuf *pixbuf = gtk_image_get_pixbuf (icon); + gtk_drag_source_set_icon_pixbuf (widget, pixbuf); + } +} + +static GtkWidget * +editor_create_item (EggToolbarEditor *editor, + GtkImage *icon, + const char *label_text, + GdkDragAction action) +{ + GtkWidget *event_box; + GtkWidget *vbox; + GtkWidget *label; + gchar *label_no_mnemonic = NULL; + + event_box = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE); + gtk_widget_show (event_box); + gtk_drag_source_set (event_box, + GDK_BUTTON1_MASK, + source_drag_types, G_N_ELEMENTS (source_drag_types), action); + g_signal_connect (event_box, "drag_data_get", + G_CALLBACK (drag_data_get_cb), editor); + g_signal_connect_after (event_box, "realize", + G_CALLBACK (event_box_realize_cb), icon); + + if (action == GDK_ACTION_MOVE) + { + g_signal_connect (event_box, "drag_begin", + G_CALLBACK (drag_begin_cb), NULL); + g_signal_connect (event_box, "drag_end", + G_CALLBACK (drag_end_cb), NULL); + } + + vbox = gtk_vbox_new (0, FALSE); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (event_box), vbox); + + gtk_widget_show (GTK_WIDGET (icon)); + gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (icon), FALSE, TRUE, 0); + label_no_mnemonic = elide_underscores (label_text); + label = gtk_label_new (label_no_mnemonic); + g_free (label_no_mnemonic); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); + + return event_box; +} + +static GtkWidget * +editor_create_item_from_name (EggToolbarEditor *editor, + const char * name, + GdkDragAction drag_action) +{ + GtkWidget *item; + const char *item_name; + char *short_label; + const char *collate_key; + + if (strcmp (name, "_separator") == 0) + { + GtkWidget *icon; + + icon = _egg_editable_toolbar_new_separator_image (); + short_label = _("Separator"); + item_name = g_strdup (name); + collate_key = g_utf8_collate_key (short_label, -1); + item = editor_create_item (editor, GTK_IMAGE (icon), + short_label, drag_action); + } + else + { + GtkAction *action; + GtkWidget *icon; + char *stock_id, *icon_name = NULL; + + action = find_action (editor, name); + g_return_val_if_fail (action != NULL, NULL); + + g_object_get (action, + "icon-name", &icon_name, + "stock-id", &stock_id, + "short-label", &short_label, + NULL); + + /* This is a workaround to catch named icons. */ + if (icon_name) + icon = gtk_image_new_from_icon_name (icon_name, + GTK_ICON_SIZE_LARGE_TOOLBAR); + else + icon = gtk_image_new_from_stock (stock_id ? stock_id : GTK_STOCK_DND, + GTK_ICON_SIZE_LARGE_TOOLBAR); + + item_name = g_strdup (name); + collate_key = g_utf8_collate_key (short_label, -1); + item = editor_create_item (editor, GTK_IMAGE (icon), + short_label, drag_action); + + g_free (short_label); + g_free (stock_id); + g_free (icon_name); + } + + g_object_set_data_full (G_OBJECT (item), "egg-collate-key", + (gpointer) collate_key, g_free); + g_object_set_data_full (G_OBJECT (item), "egg-item-name", + (gpointer) item_name, g_free); + + return item; +} + +static gint +append_table (GtkTable *table, GList *items, gint y, gint width) +{ + if (items != NULL) + { + gint x = 0, height; + GtkWidget *alignment; + GtkWidget *item; + + height = g_list_length (items) / width + 1; + gtk_table_resize (table, height, width); + + if (y > 0) + { + item = gtk_hseparator_new (); + alignment = gtk_alignment_new (0.5, 0.5, 1.0, 0.0); + gtk_container_add (GTK_CONTAINER (alignment), item); + gtk_widget_show (alignment); + gtk_widget_show (item); + + gtk_table_attach_defaults (table, alignment, 0, width, y-1, y+1); + } + + for (; items != NULL; items = items->next) + { + item = items->data; + alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_container_add (GTK_CONTAINER (alignment), item); + gtk_widget_show (alignment); + gtk_widget_show (item); + + if (x >= width) + { + x = 0; + y++; + } + gtk_table_attach_defaults (table, alignment, x, x+1, y, y+1); + x++; + } + + y++; + } + return y; +} + +static void +update_editor_sheet (EggToolbarEditor *editor) +{ + gint y; + GPtrArray *items; + GList *to_move = NULL, *to_copy = NULL; + GtkWidget *table; + GtkWidget *viewport; + + g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (editor)); + + /* Create new table. */ + table = gtk_table_new (0, 0, TRUE); + editor->priv->table = table; + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_table_set_row_spacings (GTK_TABLE (table), 24); + gtk_widget_show (table); + gtk_drag_dest_set (table, GTK_DEST_DEFAULT_ALL, + dest_drag_types, G_N_ELEMENTS (dest_drag_types), + GDK_ACTION_MOVE | GDK_ACTION_COPY); + + /* Build two lists of items (one for copying, one for moving). */ + items = egg_toolbars_model_get_name_avail (editor->priv->model); + while (items->len > 0) + { + GtkWidget *item; + const char *name; + gint flags; + + name = g_ptr_array_index (items, 0); + g_ptr_array_remove_index_fast (items, 0); + + flags = egg_toolbars_model_get_name_flags (editor->priv->model, name); + if ((flags & EGG_TB_MODEL_NAME_INFINITE) == 0) + { + item = editor_create_item_from_name (editor, name, GDK_ACTION_MOVE); + if (item != NULL) + to_move = g_list_insert_sorted (to_move, item, compare_items); + } + else + { + item = editor_create_item_from_name (editor, name, GDK_ACTION_COPY); + if (item != NULL) + to_copy = g_list_insert_sorted (to_copy, item, compare_items); + } + } + + /* Add them to the sheet. */ + y = 0; + y = append_table (GTK_TABLE (table), to_move, y, 4); + y = append_table (GTK_TABLE (table), to_copy, y, 4); + + g_list_free (to_move); + g_list_free (to_copy); + g_ptr_array_free (items, TRUE); + + /* Delete old table. */ + viewport = gtk_bin_get_child (GTK_BIN (editor->priv->scrolled_window)); + if (viewport) + { + gtk_container_remove (GTK_CONTAINER (viewport), + gtk_bin_get_child (GTK_BIN (viewport))); + } + + /* Add table to window. */ + gtk_scrolled_window_add_with_viewport + (GTK_SCROLLED_WINDOW (editor->priv->scrolled_window), table); + +} + +static void +setup_editor (EggToolbarEditor *editor) +{ + GtkWidget *scrolled_window; + + gtk_container_set_border_width (GTK_CONTAINER (editor), 12); + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + editor->priv->scrolled_window = scrolled_window; + gtk_widget_show (scrolled_window); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (editor), scrolled_window, TRUE, TRUE, 0); +} + +static void +egg_toolbar_editor_init (EggToolbarEditor *t) +{ + t->priv = EGG_TOOLBAR_EDITOR_GET_PRIVATE (t); + + t->priv->manager = NULL; + t->priv->actions_list = NULL; + + setup_editor (t); +} + diff --git a/cut-n-paste/toolbar-editor/egg-toolbar-editor.h b/cut-n-paste/toolbar-editor/egg-toolbar-editor.h new file mode 100644 index 00000000..3b897c50 --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-toolbar-editor.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003 Marco Pesenti Gritti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef EGG_TOOLBAR_EDITOR_H +#define EGG_TOOLBAR_EDITOR_H + +#include + +#include "egg-toolbars-model.h" + +G_BEGIN_DECLS + +typedef struct EggToolbarEditorClass EggToolbarEditorClass; + +#define EGG_TYPE_TOOLBAR_EDITOR (egg_toolbar_editor_get_type ()) +#define EGG_TOOLBAR_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TOOLBAR_EDITOR, EggToolbarEditor)) +#define EGG_TOOLBAR_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TOOLBAR_EDITOR, EggToolbarEditorClass)) +#define EGG_IS_TOOLBAR_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TOOLBAR_EDITOR)) +#define EGG_IS_TOOLBAR_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TOOLBAR_EDITOR)) +#define EGG_TOOLBAR_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TOOLBAR_EDITOR, EggToolbarEditorClass)) + + +typedef struct EggToolbarEditor EggToolbarEditor; +typedef struct EggToolbarEditorPrivate EggToolbarEditorPrivate; + +struct EggToolbarEditor +{ + GtkVBox parent_object; + + /*< private >*/ + EggToolbarEditorPrivate *priv; +}; + +struct EggToolbarEditorClass +{ + GtkVBoxClass parent_class; +}; + + +GType egg_toolbar_editor_get_type (void); +GtkWidget *egg_toolbar_editor_new (GtkUIManager *manager, + EggToolbarsModel *model); +void egg_toolbar_editor_set_model (EggToolbarEditor *t, + EggToolbarsModel *model); + +G_END_DECLS + +#endif diff --git a/cut-n-paste/toolbar-editor/egg-toolbars-model.c b/cut-n-paste/toolbar-editor/egg-toolbars-model.c new file mode 100644 index 00000000..27dbedf6 --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-toolbars-model.c @@ -0,0 +1,987 @@ +/* + * Copyright (C) 2002-2004 Marco Pesenti Gritti + * Copyright (C) 2004 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "egg-toolbars-model.h" +#include "eggtypebuiltins.h" +#include "eggmarshalers.h" + +#include +#include +#include +#include + +static void egg_toolbars_model_finalize (GObject *object); + +enum +{ + ITEM_ADDED, + ITEM_REMOVED, + TOOLBAR_ADDED, + TOOLBAR_CHANGED, + TOOLBAR_REMOVED, + LAST_SIGNAL +}; + +typedef struct +{ + char *name; + EggTbModelFlags flags; +} EggToolbarsToolbar; + +typedef struct +{ + char *name; +} EggToolbarsItem; + +static guint signals[LAST_SIGNAL] = { 0 }; + +#define EGG_TOOLBARS_MODEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EGG_TYPE_TOOLBARS_MODEL, EggToolbarsModelPrivate)) + +struct EggToolbarsModelPrivate +{ + GNode *toolbars; + GList *types; + GHashTable *flags; +}; + +G_DEFINE_TYPE (EggToolbarsModel, egg_toolbars_model, G_TYPE_OBJECT) + +static xmlDocPtr +egg_toolbars_model_to_xml (EggToolbarsModel *model) +{ + GNode *l1, *l2, *tl; + GList *l3; + xmlDocPtr doc; + + g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), NULL); + + tl = model->priv->toolbars; + + xmlIndentTreeOutput = TRUE; + doc = xmlNewDoc ((const xmlChar*) "1.0"); + doc->children = xmlNewDocNode (doc, NULL, (const xmlChar*) "toolbars", NULL); + + for (l1 = tl->children; l1 != NULL; l1 = l1->next) + { + xmlNodePtr tnode; + EggToolbarsToolbar *toolbar = l1->data; + + tnode = xmlNewChild (doc->children, NULL, (const xmlChar*) "toolbar", NULL); + xmlSetProp (tnode, (const xmlChar*) "name", (const xmlChar*) toolbar->name); + xmlSetProp (tnode, (const xmlChar*) "hidden", + (toolbar->flags&EGG_TB_MODEL_HIDDEN) ? (const xmlChar*) "true" : (const xmlChar*) "false"); + xmlSetProp (tnode, (const xmlChar*) "editable", + (toolbar->flags&EGG_TB_MODEL_NOT_EDITABLE) ? (const xmlChar*) "false" : (const xmlChar*) "true"); + + for (l2 = l1->children; l2 != NULL; l2 = l2->next) + { + xmlNodePtr node; + EggToolbarsItem *item = l2->data; + + if (strcmp (item->name, "_separator") == 0) + { + node = xmlNewChild (tnode, NULL, (const xmlChar*) "separator", NULL); + continue; + } + + node = xmlNewChild (tnode, NULL, (const xmlChar*) "toolitem", NULL); + xmlSetProp (node, (const xmlChar*) "name", (const xmlChar*) item->name); + + /* Add 'data' nodes for each data type which can be written out for this + * item. Only write types which can be used to restore the data. */ + for (l3 = model->priv->types; l3 != NULL; l3 = l3->next) + { + EggToolbarsItemType *type = l3->data; + if (type->get_name != NULL && type->get_data != NULL) + { + xmlNodePtr dnode; + char *tmp; + + tmp = type->get_data (type, item->name); + if (tmp != NULL) + { + dnode = xmlNewTextChild (node, NULL, (const xmlChar*) "data", (const xmlChar*) tmp); + g_free (tmp); + + tmp = gdk_atom_name (type->type); + xmlSetProp (dnode, (const xmlChar*) "type", (const xmlChar*) tmp); + g_free (tmp); + } + } + } + } + } + + return doc; +} + +static gboolean +safe_save_xml (const char *xml_file, xmlDocPtr doc) +{ + char *tmp_file; + char *old_file; + gboolean old_exist; + gboolean retval = TRUE; + + tmp_file = g_strconcat (xml_file, ".tmp", NULL); + old_file = g_strconcat (xml_file, ".old", NULL); + + if (xmlSaveFormatFile (tmp_file, doc, 1) <= 0) + { + g_warning ("Failed to write XML data to %s", tmp_file); + goto failed; + } + + old_exist = g_file_test (xml_file, G_FILE_TEST_EXISTS); + + if (old_exist) + { + if (rename (xml_file, old_file) < 0) + { + g_warning ("Failed to rename %s to %s", xml_file, old_file); + retval = FALSE; + goto failed; + } + } + + if (rename (tmp_file, xml_file) < 0) + { + g_warning ("Failed to rename %s to %s", tmp_file, xml_file); + + if (rename (old_file, xml_file) < 0) + { + g_warning ("Failed to restore %s from %s", xml_file, tmp_file); + } + retval = FALSE; + goto failed; + } + + if (old_exist) + { + if (unlink (old_file) < 0) + { + g_warning ("Failed to delete old file %s", old_file); + } + } + + failed: + g_free (old_file); + g_free (tmp_file); + + return retval; +} + +void +egg_toolbars_model_save_toolbars (EggToolbarsModel *model, + const char *xml_file, + const char *version) +{ + xmlDocPtr doc; + xmlNodePtr root; + + g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model)); + + doc = egg_toolbars_model_to_xml (model); + root = xmlDocGetRootElement (doc); + xmlSetProp (root, (const xmlChar*) "version", (const xmlChar*) version); + safe_save_xml (xml_file, doc); + xmlFreeDoc (doc); +} + +static gboolean +is_unique (EggToolbarsModel *model, + EggToolbarsItem *idata) +{ + EggToolbarsItem *idata2; + GNode *toolbar, *item; + + + for(toolbar = g_node_first_child (model->priv->toolbars); + toolbar != NULL; toolbar = g_node_next_sibling (toolbar)) + { + for(item = g_node_first_child (toolbar); + item != NULL; item = g_node_next_sibling (item)) + { + idata2 = item->data; + + if (idata != idata2 && strcmp (idata->name, idata2->name) == 0) + { + return FALSE; + } + } + } + + return TRUE; +} + +static GNode * +toolbar_node_new (const char *name) +{ + EggToolbarsToolbar *toolbar; + + toolbar = g_new (EggToolbarsToolbar, 1); + toolbar->name = g_strdup (name); + toolbar->flags = 0; + + return g_node_new (toolbar); +} + +static GNode * +item_node_new (const char *name, EggToolbarsModel *model) +{ + EggToolbarsItem *item; + int flags; + + g_return_val_if_fail (name != NULL, NULL); + + item = g_new (EggToolbarsItem, 1); + item->name = g_strdup (name); + + flags = GPOINTER_TO_INT (g_hash_table_lookup (model->priv->flags, item->name)); + if ((flags & EGG_TB_MODEL_NAME_INFINITE) == 0) + g_hash_table_insert (model->priv->flags, + g_strdup (item->name), + GINT_TO_POINTER (flags | EGG_TB_MODEL_NAME_USED)); + + return g_node_new (item); +} + +static void +item_node_free (GNode *item_node, EggToolbarsModel *model) +{ + EggToolbarsItem *item = item_node->data; + int flags; + + flags = GPOINTER_TO_INT (g_hash_table_lookup (model->priv->flags, item->name)); + if ((flags & EGG_TB_MODEL_NAME_INFINITE) == 0 && is_unique (model, item)) + g_hash_table_insert (model->priv->flags, + g_strdup (item->name), + GINT_TO_POINTER (flags & ~EGG_TB_MODEL_NAME_USED)); + + g_free (item->name); + g_free (item); + + g_node_destroy (item_node); +} + +static void +toolbar_node_free (GNode *toolbar_node, EggToolbarsModel *model) +{ + EggToolbarsToolbar *toolbar = toolbar_node->data; + + g_node_children_foreach (toolbar_node, G_TRAVERSE_ALL, + (GNodeForeachFunc) item_node_free, model); + + g_free (toolbar->name); + g_free (toolbar); + + g_node_destroy (toolbar_node); +} + +EggTbModelFlags +egg_toolbars_model_get_flags (EggToolbarsModel *model, + int toolbar_position) +{ + GNode *toolbar_node; + EggToolbarsToolbar *toolbar; + + toolbar_node = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_val_if_fail (toolbar_node != NULL, 0); + + toolbar = toolbar_node->data; + + return toolbar->flags; +} + +void +egg_toolbars_model_set_flags (EggToolbarsModel *model, + int toolbar_position, + EggTbModelFlags flags) +{ + GNode *toolbar_node; + EggToolbarsToolbar *toolbar; + + toolbar_node = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_if_fail (toolbar_node != NULL); + + toolbar = toolbar_node->data; + + toolbar->flags = flags; + + g_signal_emit (G_OBJECT (model), signals[TOOLBAR_CHANGED], + 0, toolbar_position); +} + + +char * +egg_toolbars_model_get_data (EggToolbarsModel *model, + GdkAtom type, + const char *name) +{ + EggToolbarsItemType *t; + char *data = NULL; + GList *l; + + if (type == GDK_NONE || type == gdk_atom_intern (EGG_TOOLBAR_ITEM_TYPE, FALSE)) + { + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (*name != 0, NULL); + return strdup (name); + } + + for (l = model->priv->types; l != NULL; l = l->next) + { + t = l->data; + if (t->type == type && t->get_data != NULL) + { + data = t->get_data (t, name); + if (data != NULL) break; + } + } + + return data; +} + +char * +egg_toolbars_model_get_name (EggToolbarsModel *model, + GdkAtom type, + const char *data, + gboolean create) +{ + EggToolbarsItemType *t; + char *name = NULL; + GList *l; + + if (type == GDK_NONE || type == gdk_atom_intern (EGG_TOOLBAR_ITEM_TYPE, FALSE)) + { + g_return_val_if_fail (data, NULL); + g_return_val_if_fail (*data, NULL); + return strdup (data); + } + + if (create) + { + for (l = model->priv->types; name == NULL && l != NULL; l = l->next) + { + t = l->data; + if (t->type == type && t->new_name != NULL) + name = t->new_name (t, data); + } + + return name; + } + else + { + for (l = model->priv->types; name == NULL && l != NULL; l = l->next) + { + t = l->data; + if (t->type == type && t->get_name != NULL) + name = t->get_name (t, data); + } + + return name; + } +} + +static gboolean +impl_add_item (EggToolbarsModel *model, + int toolbar_position, + int position, + const char *name) +{ + GNode *parent_node; + GNode *child_node; + int real_position; + + g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + parent_node = g_node_nth_child (model->priv->toolbars, toolbar_position); + child_node = item_node_new (name, model); + g_node_insert (parent_node, position, child_node); + + real_position = g_node_child_position (parent_node, child_node); + + g_signal_emit (G_OBJECT (model), signals[ITEM_ADDED], 0, + toolbar_position, real_position); + + return TRUE; +} + +gboolean +egg_toolbars_model_add_item (EggToolbarsModel *model, + int toolbar_position, + int position, + const char *name) +{ + EggToolbarsModelClass *klass = EGG_TOOLBARS_MODEL_GET_CLASS (model); + return klass->add_item (model, toolbar_position, position, name); +} + +int +egg_toolbars_model_add_toolbar (EggToolbarsModel *model, + int position, + const char *name) +{ + GNode *node; + int real_position; + + g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), -1); + + node = toolbar_node_new (name); + g_node_insert (model->priv->toolbars, position, node); + + real_position = g_node_child_position (model->priv->toolbars, node); + + g_signal_emit (G_OBJECT (model), signals[TOOLBAR_ADDED], + 0, real_position); + + return g_node_child_position (model->priv->toolbars, node); +} + +static char * +parse_data_list (EggToolbarsModel *model, + xmlNodePtr child, + gboolean create) +{ + char *name = NULL; + while (child && name == NULL) + { + if (xmlStrEqual (child->name, (const xmlChar*) "data")) + { + xmlChar *type = xmlGetProp (child, (const xmlChar*) "type"); + xmlChar *data = xmlNodeGetContent (child); + + if (type != NULL) + { + GdkAtom atom = gdk_atom_intern ((const char*) type, TRUE); + name = egg_toolbars_model_get_name (model, atom, (const char*) data, create); + } + + xmlFree (type); + xmlFree (data); + } + + child = child->next; + } + + return name; +} + +static void +parse_item_list (EggToolbarsModel *model, + xmlNodePtr child, + int position) +{ + while (child) + { + if (xmlStrEqual (child->name, (const xmlChar*) "toolitem")) + { + char *name; + + /* Try to get the name using the data elements first, + as they are more 'portable' or 'persistent'. */ + name = parse_data_list (model, child->children, FALSE); + if (name == NULL) + { + name = parse_data_list (model, child->children, TRUE); + } + + /* If that fails, try to use the name. */ + if (name == NULL) + { + xmlChar *type = xmlGetProp (child, (const xmlChar*) "type"); + xmlChar *data = xmlGetProp (child, (const xmlChar*) "name"); + GdkAtom atom = type ? gdk_atom_intern ((const char*) type, TRUE) : GDK_NONE; + + /* If an old format, try to use it. */ + name = egg_toolbars_model_get_name (model, atom, (const char*) data, FALSE); + if (name == NULL) + { + name = egg_toolbars_model_get_name (model, atom, (const char*) data, TRUE); + } + + xmlFree (type); + xmlFree (data); + } + + if (name != NULL) + { + egg_toolbars_model_add_item (model, position, -1, name); + g_free (name); + } + } + else if (xmlStrEqual (child->name, (const xmlChar*) "separator")) + { + egg_toolbars_model_add_item (model, position, -1, "_separator"); + } + + child = child->next; + } +} + +static void +parse_toolbars (EggToolbarsModel *model, + xmlNodePtr child) +{ + while (child) + { + if (xmlStrEqual (child->name, (const xmlChar*) "toolbar")) + { + xmlChar *string; + int position; + EggTbModelFlags flags; + + string = xmlGetProp (child, (const xmlChar*) "name"); + position = egg_toolbars_model_add_toolbar (model, -1, (const char*) string); + flags = egg_toolbars_model_get_flags (model, position); + xmlFree (string); + + string = xmlGetProp (child, (const xmlChar*) "editable"); + if (string && xmlStrEqual (string, (const xmlChar*) "false")) + flags |= EGG_TB_MODEL_NOT_EDITABLE; + xmlFree (string); + + string = xmlGetProp (child, (const xmlChar*) "hidden"); + if (string && xmlStrEqual (string, (const xmlChar*) "true")) + flags |= EGG_TB_MODEL_HIDDEN; + xmlFree (string); + + string = xmlGetProp (child, (const xmlChar*) "style"); + if (string && xmlStrEqual (string, (const xmlChar*) "icons-only")) + flags |= EGG_TB_MODEL_ICONS; + xmlFree (string); + + egg_toolbars_model_set_flags (model, position, flags); + + parse_item_list (model, child->children, position); + } + + child = child->next; + } +} + +gboolean +egg_toolbars_model_load_toolbars (EggToolbarsModel *model, + const char *xml_file) +{ + xmlDocPtr doc; + xmlNodePtr root; + + g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), FALSE); + + if (!xml_file || !g_file_test (xml_file, G_FILE_TEST_EXISTS)) return FALSE; + + doc = xmlParseFile (xml_file); + if (doc == NULL) + { + g_warning ("Failed to load XML data from %s", xml_file); + return FALSE; + } + root = xmlDocGetRootElement (doc); + + parse_toolbars (model, root->children); + + xmlFreeDoc (doc); + + return TRUE; +} + +static void +parse_available_list (EggToolbarsModel *model, + xmlNodePtr child) +{ + gint flags; + + while (child) + { + if (xmlStrEqual (child->name, (const xmlChar*) "toolitem")) + { + xmlChar *name; + + name = xmlGetProp (child, (const xmlChar*) "name"); + flags = egg_toolbars_model_get_name_flags + (model, (const char*)name); + egg_toolbars_model_set_name_flags + (model, (const char*)name, flags | EGG_TB_MODEL_NAME_KNOWN); + xmlFree (name); + } + child = child->next; + } +} + +static void +parse_names (EggToolbarsModel *model, + xmlNodePtr child) +{ + while (child) + { + if (xmlStrEqual (child->name, (const xmlChar*) "available")) + { + parse_available_list (model, child->children); + } + + child = child->next; + } +} + +gboolean +egg_toolbars_model_load_names (EggToolbarsModel *model, + const char *xml_file) +{ + xmlDocPtr doc; + xmlNodePtr root; + + g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), FALSE); + + if (!xml_file || !g_file_test (xml_file, G_FILE_TEST_EXISTS)) return FALSE; + + doc = xmlParseFile (xml_file); + if (doc == NULL) + { + g_warning ("Failed to load XML data from %s", xml_file); + return FALSE; + } + root = xmlDocGetRootElement (doc); + + parse_names (model, root->children); + + xmlFreeDoc (doc); + + return TRUE; +} + +static void +egg_toolbars_model_class_init (EggToolbarsModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + volatile GType flags_type; /* work around gcc's optimiser */ + + /* make sure the flags type is known */ + flags_type = EGG_TYPE_TB_MODEL_FLAGS; + + object_class->finalize = egg_toolbars_model_finalize; + + klass->add_item = impl_add_item; + + signals[ITEM_ADDED] = + g_signal_new ("item_added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggToolbarsModelClass, item_added), + NULL, NULL, _egg_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); + signals[TOOLBAR_ADDED] = + g_signal_new ("toolbar_added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggToolbarsModelClass, toolbar_added), + NULL, NULL, g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + signals[ITEM_REMOVED] = + g_signal_new ("item_removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggToolbarsModelClass, item_removed), + NULL, NULL, _egg_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); + signals[TOOLBAR_REMOVED] = + g_signal_new ("toolbar_removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggToolbarsModelClass, toolbar_removed), + NULL, NULL, g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + signals[TOOLBAR_CHANGED] = + g_signal_new ("toolbar_changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggToolbarsModelClass, toolbar_changed), + NULL, NULL, g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + + g_type_class_add_private (object_class, sizeof (EggToolbarsModelPrivate)); +} + +static void +egg_toolbars_model_init (EggToolbarsModel *model) +{ + model->priv =EGG_TOOLBARS_MODEL_GET_PRIVATE (model); + + model->priv->toolbars = g_node_new (NULL); + model->priv->flags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + egg_toolbars_model_set_name_flags (model, "_separator", + EGG_TB_MODEL_NAME_KNOWN | + EGG_TB_MODEL_NAME_INFINITE); +} + +static void +egg_toolbars_model_finalize (GObject *object) +{ + EggToolbarsModel *model = EGG_TOOLBARS_MODEL (object); + + g_node_children_foreach (model->priv->toolbars, G_TRAVERSE_ALL, + (GNodeForeachFunc) toolbar_node_free, model); + g_node_destroy (model->priv->toolbars); + g_hash_table_destroy (model->priv->flags); + + G_OBJECT_CLASS (egg_toolbars_model_parent_class)->finalize (object); +} + +EggToolbarsModel * +egg_toolbars_model_new (void) +{ + return EGG_TOOLBARS_MODEL (g_object_new (EGG_TYPE_TOOLBARS_MODEL, NULL)); +} + +void +egg_toolbars_model_remove_toolbar (EggToolbarsModel *model, + int position) +{ + GNode *node; + EggTbModelFlags flags; + + g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model)); + + flags = egg_toolbars_model_get_flags (model, position); + + if (!(flags & EGG_TB_MODEL_NOT_REMOVABLE)) + { + node = g_node_nth_child (model->priv->toolbars, position); + g_return_if_fail (node != NULL); + + toolbar_node_free (node, model); + + g_signal_emit (G_OBJECT (model), signals[TOOLBAR_REMOVED], + 0, position); + } +} + +void +egg_toolbars_model_remove_item (EggToolbarsModel *model, + int toolbar_position, + int position) +{ + GNode *node, *toolbar; + + g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model)); + + toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_if_fail (toolbar != NULL); + + node = g_node_nth_child (toolbar, position); + g_return_if_fail (node != NULL); + + item_node_free (node, model); + + g_signal_emit (G_OBJECT (model), signals[ITEM_REMOVED], 0, + toolbar_position, position); +} + +void +egg_toolbars_model_move_item (EggToolbarsModel *model, + int toolbar_position, + int position, + int new_toolbar_position, + int new_position) +{ + GNode *node, *toolbar, *new_toolbar; + + g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model)); + + toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_if_fail (toolbar != NULL); + + new_toolbar = g_node_nth_child (model->priv->toolbars, new_toolbar_position); + g_return_if_fail (new_toolbar != NULL); + + node = g_node_nth_child (toolbar, position); + g_return_if_fail (node != NULL); + + g_node_unlink (node); + + g_signal_emit (G_OBJECT (model), signals[ITEM_REMOVED], 0, + toolbar_position, position); + + g_node_insert (new_toolbar, new_position, node); + + g_signal_emit (G_OBJECT (model), signals[ITEM_ADDED], 0, + new_toolbar_position, new_position); +} + +void +egg_toolbars_model_delete_item (EggToolbarsModel *model, + const char *name) +{ + EggToolbarsItem *idata; + EggToolbarsToolbar *tdata; + GNode *toolbar, *item, *next; + int tpos, ipos; + + g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model)); + + toolbar = g_node_first_child (model->priv->toolbars); + tpos = 0; + + while (toolbar != NULL) + { + item = g_node_first_child (toolbar); + ipos = 0; + + /* Don't delete toolbars that were already empty */ + if (item == NULL) + { + toolbar = g_node_next_sibling (toolbar); + continue; + } + + while (item != NULL) + { + next = g_node_next_sibling (item); + idata = item->data; + if (strcmp (idata->name, name) == 0) + { + item_node_free (item, model); + g_signal_emit (G_OBJECT (model), + signals[ITEM_REMOVED], + 0, tpos, ipos); + } + else + { + ipos++; + } + + item = next; + } + + next = g_node_next_sibling (toolbar); + tdata = toolbar->data; + if (!(tdata->flags & EGG_TB_MODEL_NOT_REMOVABLE) && + g_node_first_child (toolbar) == NULL) + { + toolbar_node_free (toolbar, model); + + g_signal_emit (G_OBJECT (model), + signals[TOOLBAR_REMOVED], + 0, tpos); + } + else + { + tpos++; + } + + toolbar = next; + } +} + +int +egg_toolbars_model_n_items (EggToolbarsModel *model, + int toolbar_position) +{ + GNode *toolbar; + + toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_val_if_fail (toolbar != NULL, -1); + + return g_node_n_children (toolbar); +} + +const char * +egg_toolbars_model_item_nth (EggToolbarsModel *model, + int toolbar_position, + int position) +{ + GNode *toolbar; + GNode *item; + EggToolbarsItem *idata; + + toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position); + g_return_val_if_fail (toolbar != NULL, NULL); + + item = g_node_nth_child (toolbar, position); + g_return_val_if_fail (item != NULL, NULL); + + idata = item->data; + return idata->name; +} + +int +egg_toolbars_model_n_toolbars (EggToolbarsModel *model) +{ + return g_node_n_children (model->priv->toolbars); +} + +const char * +egg_toolbars_model_toolbar_nth (EggToolbarsModel *model, + int position) +{ + GNode *toolbar; + EggToolbarsToolbar *tdata; + + toolbar = g_node_nth_child (model->priv->toolbars, position); + g_return_val_if_fail (toolbar != NULL, NULL); + + tdata = toolbar->data; + + return tdata->name; +} + +GList * +egg_toolbars_model_get_types (EggToolbarsModel *model) +{ + return model->priv->types; +} + +void +egg_toolbars_model_set_types (EggToolbarsModel *model, GList *types) +{ + model->priv->types = types; +} + +static void +fill_avail_array (gpointer key, gpointer value, GPtrArray *array) +{ + int flags = GPOINTER_TO_INT (value); + if ((flags & EGG_TB_MODEL_NAME_KNOWN) && !(flags & EGG_TB_MODEL_NAME_USED)) + g_ptr_array_add (array, key); +} + +GPtrArray * +egg_toolbars_model_get_name_avail (EggToolbarsModel *model) +{ + GPtrArray *array = g_ptr_array_new (); + g_hash_table_foreach (model->priv->flags, (GHFunc) fill_avail_array, array); + return array; +} + +gint +egg_toolbars_model_get_name_flags (EggToolbarsModel *model, const char *name) +{ + return GPOINTER_TO_INT (g_hash_table_lookup (model->priv->flags, name)); +} + +void +egg_toolbars_model_set_name_flags (EggToolbarsModel *model, const char *name, gint flags) +{ + g_hash_table_insert (model->priv->flags, g_strdup (name), GINT_TO_POINTER (flags)); +} diff --git a/cut-n-paste/toolbar-editor/egg-toolbars-model.h b/cut-n-paste/toolbar-editor/egg-toolbars-model.h new file mode 100644 index 00000000..5d9841f8 --- /dev/null +++ b/cut-n-paste/toolbar-editor/egg-toolbars-model.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2003-2004 Marco Pesenti Gritti + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#ifndef EGG_TOOLBARS_MODEL_H +#define EGG_TOOLBARS_MODEL_H + +#include +#include +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_TOOLBARS_MODEL (egg_toolbars_model_get_type ()) +#define EGG_TOOLBARS_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TOOLBARS_MODEL, EggToolbarsModel)) +#define EGG_TOOLBARS_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TOOLBARS_MODEL, EggToolbarsModelClass)) +#define EGG_IS_TOOLBARS_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TOOLBARS_MODEL)) +#define EGG_IS_TOOLBARS_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TOOLBARS_MODEL)) +#define EGG_TOOLBARS_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TOOLBARS_MODEL, EggToolbarsModelClass)) + +typedef struct EggToolbarsModel EggToolbarsModel; +typedef struct EggToolbarsModelPrivate EggToolbarsModelPrivate; +typedef struct EggToolbarsModelClass EggToolbarsModelClass; + +#define EGG_TOOLBAR_ITEM_TYPE "application/x-toolbar-item" + +typedef enum +{ + EGG_TB_MODEL_NOT_REMOVABLE = 1 << 0, + EGG_TB_MODEL_NOT_EDITABLE = 1 << 1, + EGG_TB_MODEL_BOTH = 1 << 2, + EGG_TB_MODEL_BOTH_HORIZ = 1 << 3, + EGG_TB_MODEL_ICONS = 1 << 4, + EGG_TB_MODEL_TEXT = 1 << 5, + EGG_TB_MODEL_STYLES_MASK = 0x3C, + EGG_TB_MODEL_ACCEPT_ITEMS_ONLY = 1 << 6, + EGG_TB_MODEL_HIDDEN = 1 << 7 +} EggTbModelFlags; + +typedef enum +{ + EGG_TB_MODEL_NAME_USED = 1 << 0, + EGG_TB_MODEL_NAME_INFINITE = 1 << 1, + EGG_TB_MODEL_NAME_KNOWN = 1 << 2 +} EggTbModelNameFlags; + +struct EggToolbarsModel +{ + GObject parent_object; + + /*< private >*/ + EggToolbarsModelPrivate *priv; +}; + +struct EggToolbarsModelClass +{ + GObjectClass parent_class; + + /* Signals */ + void (* item_added) (EggToolbarsModel *model, + int toolbar_position, + int position); + void (* item_removed) (EggToolbarsModel *model, + int toolbar_position, + int position); + void (* toolbar_added) (EggToolbarsModel *model, + int position); + void (* toolbar_changed) (EggToolbarsModel *model, + int position); + void (* toolbar_removed) (EggToolbarsModel *model, + int position); + + /* Virtual Table */ + gboolean (* add_item) (EggToolbarsModel *t, + int toolbar_position, + int position, + const char *name); +}; + +typedef struct EggToolbarsItemType EggToolbarsItemType; + +struct EggToolbarsItemType +{ + GdkAtom type; + + gboolean (* has_data) (EggToolbarsItemType *type, + const char *name); + char * (* get_data) (EggToolbarsItemType *type, + const char *name); + + char * (* new_name) (EggToolbarsItemType *type, + const char *data); + char * (* get_name) (EggToolbarsItemType *type, + const char *data); +}; + +GType egg_tb_model_flags_get_type (void); +GType egg_toolbars_model_get_type (void); +EggToolbarsModel *egg_toolbars_model_new (void); +gboolean egg_toolbars_model_load_names (EggToolbarsModel *model, + const char *xml_file); +gboolean egg_toolbars_model_load_toolbars (EggToolbarsModel *model, + const char *xml_file); +void egg_toolbars_model_save_toolbars (EggToolbarsModel *model, + const char *xml_file, + const char *version); + +/* Functions for manipulating the types of portable data this toolbar understands. */ +GList * egg_toolbars_model_get_types (EggToolbarsModel *model); +void egg_toolbars_model_set_types (EggToolbarsModel *model, + GList *types); + +/* Functions for converting between name and portable data. */ +char * egg_toolbars_model_get_name (EggToolbarsModel *model, + GdkAtom type, + const char *data, + gboolean create); +char * egg_toolbars_model_get_data (EggToolbarsModel *model, + GdkAtom type, + const char *name); + +/* Functions for retrieving what items are available for adding to the toolbars. */ +GPtrArray * egg_toolbars_model_get_name_avail (EggToolbarsModel *model); +gint egg_toolbars_model_get_name_flags (EggToolbarsModel *model, + const char *name); +void egg_toolbars_model_set_name_flags (EggToolbarsModel *model, + const char *name, + gint flags); + +/* Functions for manipulating flags on individual toolbars. */ +EggTbModelFlags egg_toolbars_model_get_flags (EggToolbarsModel *model, + int toolbar_position); +void egg_toolbars_model_set_flags (EggToolbarsModel *model, + int toolbar_position, + EggTbModelFlags flags); + +/* Functions for adding and removing toolbars. */ +int egg_toolbars_model_add_toolbar (EggToolbarsModel *model, + int position, + const char *name); +void egg_toolbars_model_remove_toolbar (EggToolbarsModel *model, + int position); + +/* Functions for adding, removing and moving items. */ +gboolean egg_toolbars_model_add_item (EggToolbarsModel *model, + int toolbar_position, + int position, + const char *name); +void egg_toolbars_model_remove_item (EggToolbarsModel *model, + int toolbar_position, + int position); +void egg_toolbars_model_move_item (EggToolbarsModel *model, + int toolbar_position, + int position, + int new_toolbar_position, + int new_position); +void egg_toolbars_model_delete_item (EggToolbarsModel *model, + const char *name); + +/* Functions for accessing the names of items. */ +int egg_toolbars_model_n_items (EggToolbarsModel *model, + int toolbar_position); +const char * egg_toolbars_model_item_nth (EggToolbarsModel *model, + int toolbar_position, + int position); + +/* Functions for accessing the names of toolbars. */ +int egg_toolbars_model_n_toolbars (EggToolbarsModel *model); +const char *egg_toolbars_model_toolbar_nth (EggToolbarsModel *model, + int position); + +G_END_DECLS + +#endif diff --git a/cut-n-paste/toolbar-editor/eggmarshalers.list b/cut-n-paste/toolbar-editor/eggmarshalers.list new file mode 100644 index 00000000..1f953ddf --- /dev/null +++ b/cut-n-paste/toolbar-editor/eggmarshalers.list @@ -0,0 +1 @@ +VOID:INT,INT diff --git a/cut-n-paste/totem-screensaver/Makefile.am b/cut-n-paste/totem-screensaver/Makefile.am new file mode 100644 index 00000000..1304c8d4 --- /dev/null +++ b/cut-n-paste/totem-screensaver/Makefile.am @@ -0,0 +1,16 @@ +noinst_LTLIBRARIES = libtotemscrsaver.la +libtotemscrsaver_la_SOURCES = \ + totem-scrsaver.h \ + totem-scrsaver.c + +libtotemscrsaver_la_CPPFLAGS = \ + $(AM_CPPFLAGS) + +libtotemscrsaver_la_CFLAGS = \ + $(SHELL_CORE_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(WARNING_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + $(AM_CFLAGS) + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/totem-screensaver/README b/cut-n-paste/totem-screensaver/README new file mode 100644 index 00000000..a5be11b2 --- /dev/null +++ b/cut-n-paste/totem-screensaver/README @@ -0,0 +1,3 @@ +The sources for the screensaver enabling/disabling code are copied from Totem. +A simple replacement (s/WITH_DBUS/ENABLE_DBUS/g) was needed. The hardcoded +"reason for inhibiting" string was also modified. diff --git a/cut-n-paste/totem-screensaver/totem-scrsaver.c b/cut-n-paste/totem-screensaver/totem-scrsaver.c new file mode 100644 index 00000000..f30f533e --- /dev/null +++ b/cut-n-paste/totem-screensaver/totem-scrsaver.c @@ -0,0 +1,554 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + + Copyright (C) 2004-2006 Bastien Nocera + Copyright Ā© 2010 Christian Persch + Copyright Ā© 2010 Carlos Garcia Campos + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301 USA. + + Authors: Bastien Nocera + Christian Persch + Carlos Garcia Campos + */ + +#include "config.h" + +#include + +#ifdef GDK_WINDOWING_X11 +#include +#include + +#ifdef HAVE_XTEST +#include +#endif /* HAVE_XTEST */ +#endif /* GDK_WINDOWING_X11 */ + +#include "totem-scrsaver.h" + +#define GS_SERVICE "org.mate.ScreenSaver" +#define GS_PATH "/org/mate/ScreenSaver" +#define GS_INTERFACE "org.mate.ScreenSaver" + +#define XSCREENSAVER_MIN_TIMEOUT 60 + +enum { + PROP_0, + PROP_REASON +}; + +static void totem_scrsaver_finalize (GObject *object); + +struct TotemScrsaverPrivate { + /* Whether the screensaver is disabled */ + gboolean disabled; + /* The reason for the inhibition */ + char *reason; + + GDBusProxy *gs_proxy; + gboolean have_screensaver_dbus; + guint32 cookie; + gboolean old_dbus_api; + + /* To save the screensaver info */ + int timeout; + int interval; + int prefer_blanking; + int allow_exposures; + + /* For use with XTest */ + int keycode1, keycode2; + int *keycode; + gboolean have_xtest; +}; + +G_DEFINE_TYPE(TotemScrsaver, totem_scrsaver, G_TYPE_OBJECT) + +static gboolean +screensaver_is_running_dbus (TotemScrsaver *scr) +{ + return scr->priv->have_screensaver_dbus; +} + +static void +on_inhibit_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source_object); + TotemScrsaver *scr = TOTEM_SCRSAVER (user_data); + GVariant *value; + GError *error = NULL; + + value = g_dbus_proxy_call_finish (proxy, res, &error); + if (!value) { + if (!scr->priv->old_dbus_api && + g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { + g_return_if_fail (scr->priv->reason != NULL); + /* try the old API */ + scr->priv->old_dbus_api = TRUE; + g_dbus_proxy_call (proxy, + "InhibitActivation", + g_variant_new ("(s)", + scr->priv->reason), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + on_inhibit_cb, + scr); + } else { + g_warning ("Problem inhibiting the screensaver: %s", error->message); + } + g_error_free (error); + + return; + } + + /* save the cookie */ + if (g_variant_is_of_type (value, G_VARIANT_TYPE ("(u)"))) + g_variant_get (value, "(u)", &scr->priv->cookie); + else + scr->priv->cookie = 0; + g_variant_unref (value); +} + +static void +on_uninhibit_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source_object); + TotemScrsaver *scr = TOTEM_SCRSAVER (user_data); + GVariant *value; + GError *error = NULL; + + value = g_dbus_proxy_call_finish (proxy, res, &error); + if (!value) { + if (!scr->priv->old_dbus_api && + g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { + /* try the old API */ + scr->priv->old_dbus_api = TRUE; + g_dbus_proxy_call (proxy, + "AllowActivation", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + on_uninhibit_cb, + scr); + } else { + g_warning ("Problem uninhibiting the screensaver: %s", error->message); + } + g_error_free (error); + + return; + } + + /* clear the cookie */ + scr->priv->cookie = 0; + g_variant_unref (value); +} + +static void +screensaver_inhibit_dbus (TotemScrsaver *scr, + gboolean inhibit) +{ + TotemScrsaverPrivate *priv = scr->priv; + + if (!priv->have_screensaver_dbus) + return; + + scr->priv->old_dbus_api = FALSE; + + if (inhibit) { + g_return_if_fail (scr->priv->reason != NULL); + g_dbus_proxy_call (priv->gs_proxy, + "Inhibit", + g_variant_new ("(ss)", + g_get_application_name (), + scr->priv->reason), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + on_inhibit_cb, + scr); + } else { + g_dbus_proxy_call (priv->gs_proxy, + "UnInhibit", + g_variant_new ("(u)", priv->cookie), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + on_uninhibit_cb, + scr); + } +} + +static void +screensaver_enable_dbus (TotemScrsaver *scr) +{ + screensaver_inhibit_dbus (scr, FALSE); +} + +static void +screensaver_disable_dbus (TotemScrsaver *scr) +{ + screensaver_inhibit_dbus (scr, TRUE); +} + +static void +screensaver_update_dbus_presence (TotemScrsaver *scr) +{ + TotemScrsaverPrivate *priv = scr->priv; + gchar *name_owner; + + name_owner = g_dbus_proxy_get_name_owner (priv->gs_proxy); + if (name_owner) { + priv->have_screensaver_dbus = TRUE; + g_free (name_owner); + } else { + priv->have_screensaver_dbus = FALSE; + } +} + +static void +screensaver_dbus_owner_changed_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + TotemScrsaver *scr = TOTEM_SCRSAVER (user_data); + + screensaver_update_dbus_presence (scr); +} + +static void +screensaver_dbus_proxy_new_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + TotemScrsaver *scr = TOTEM_SCRSAVER (user_data); + TotemScrsaverPrivate *priv = scr->priv; + + priv->gs_proxy = g_dbus_proxy_new_for_bus_finish (result, NULL); + if (!priv->gs_proxy) + return; + + screensaver_update_dbus_presence (scr); + + g_signal_connect (priv->gs_proxy, "notify::g-name-owner", + G_CALLBACK (screensaver_dbus_owner_changed_cb), + scr); +} + +static void +screensaver_init_dbus (TotemScrsaver *scr) +{ + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + GS_SERVICE, + GS_PATH, + GS_INTERFACE, + NULL, + screensaver_dbus_proxy_new_cb, + scr); +} + +static void +screensaver_finalize_dbus (TotemScrsaver *scr) +{ + if (scr->priv->gs_proxy) { + g_object_unref (scr->priv->gs_proxy); + } +} + +#ifdef GDK_WINDOWING_X11 +static void +screensaver_enable_x11 (TotemScrsaver *scr) +{ + +#ifdef HAVE_XTEST + if (scr->priv->have_xtest != FALSE) + { + g_source_remove_by_user_data (scr); + return; + } +#endif /* HAVE_XTEST */ + + XLockDisplay (GDK_DISPLAY()); + XSetScreenSaver (GDK_DISPLAY(), + scr->priv->timeout, + scr->priv->interval, + scr->priv->prefer_blanking, + scr->priv->allow_exposures); + XUnlockDisplay (GDK_DISPLAY()); +} + +#ifdef HAVE_XTEST +static gboolean +fake_event (TotemScrsaver *scr) +{ + if (scr->priv->disabled) + { + XLockDisplay (GDK_DISPLAY()); + XTestFakeKeyEvent (GDK_DISPLAY(), *scr->priv->keycode, + True, CurrentTime); + XTestFakeKeyEvent (GDK_DISPLAY(), *scr->priv->keycode, + False, CurrentTime); + XUnlockDisplay (GDK_DISPLAY()); + /* Swap the keycode */ + if (scr->priv->keycode == &scr->priv->keycode1) + scr->priv->keycode = &scr->priv->keycode2; + else + scr->priv->keycode = &scr->priv->keycode1; + } + + return TRUE; +} +#endif /* HAVE_XTEST */ + +static void +screensaver_disable_x11 (TotemScrsaver *scr) +{ + +#ifdef HAVE_XTEST + if (scr->priv->have_xtest != FALSE) + { + XLockDisplay (GDK_DISPLAY()); + XGetScreenSaver(GDK_DISPLAY(), &scr->priv->timeout, + &scr->priv->interval, + &scr->priv->prefer_blanking, + &scr->priv->allow_exposures); + XUnlockDisplay (GDK_DISPLAY()); + + if (scr->priv->timeout != 0) { + g_timeout_add_seconds (scr->priv->timeout / 2, + (GSourceFunc) fake_event, scr); + } else { + g_timeout_add_seconds (XSCREENSAVER_MIN_TIMEOUT / 2, + (GSourceFunc) fake_event, scr); + } + + return; + } +#endif /* HAVE_XTEST */ + + XLockDisplay (GDK_DISPLAY()); + XGetScreenSaver(GDK_DISPLAY(), &scr->priv->timeout, + &scr->priv->interval, + &scr->priv->prefer_blanking, + &scr->priv->allow_exposures); + XSetScreenSaver(GDK_DISPLAY(), 0, 0, + DontPreferBlanking, DontAllowExposures); + XUnlockDisplay (GDK_DISPLAY()); +} + +static void +screensaver_init_x11 (TotemScrsaver *scr) +{ +#ifdef HAVE_XTEST + int a, b, c, d; + + XLockDisplay (GDK_DISPLAY()); + scr->priv->have_xtest = (XTestQueryExtension (GDK_DISPLAY(), &a, &b, &c, &d) == True); + if (scr->priv->have_xtest != FALSE) + { + scr->priv->keycode1 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L); + if (scr->priv->keycode1 == 0) { + g_warning ("scr->priv->keycode1 not existant"); + } + scr->priv->keycode2 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_R); + if (scr->priv->keycode2 == 0) { + scr->priv->keycode2 = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L); + if (scr->priv->keycode2 == 0) { + g_warning ("scr->priv->keycode2 not existant"); + } + } + scr->priv->keycode = &scr->priv->keycode1; + } + XUnlockDisplay (GDK_DISPLAY()); +#endif /* HAVE_XTEST */ +} + +static void +screensaver_finalize_x11 (TotemScrsaver *scr) +{ + g_source_remove_by_user_data (scr); +} +#endif + +static void +totem_scrsaver_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TotemScrsaver *scr; + + scr = TOTEM_SCRSAVER (object); + + switch (property_id) + { + case PROP_REASON: + g_value_set_string (value, scr->priv->reason); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +totem_scrsaver_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TotemScrsaver *scr; + + scr = TOTEM_SCRSAVER (object); + + switch (property_id) + { + case PROP_REASON: + g_free (scr->priv->reason); + scr->priv->reason = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +totem_scrsaver_class_init (TotemScrsaverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TotemScrsaverPrivate)); + + object_class->set_property = totem_scrsaver_set_property; + object_class->get_property = totem_scrsaver_get_property; + object_class->finalize = totem_scrsaver_finalize; + + g_object_class_install_property (object_class, PROP_REASON, + g_param_spec_string ("reason", NULL, NULL, + NULL, G_PARAM_READWRITE)); + +} + +/** + * totem_scrsaver_new: + * + * Creates a #TotemScrsaver object. + * If the MATE screen saver is running, it uses its DBUS interface to + * inhibit the screensaver; otherwise it falls back to using the X + * screensaver functionality for this. + * + * Returns: a newly created #TotemScrsaver + */ +TotemScrsaver * +totem_scrsaver_new (void) +{ + return TOTEM_SCRSAVER (g_object_new (TOTEM_TYPE_SCRSAVER, NULL)); +} + +static void +totem_scrsaver_init (TotemScrsaver *scr) +{ + scr->priv = G_TYPE_INSTANCE_GET_PRIVATE (scr, + TOTEM_TYPE_SCRSAVER, + TotemScrsaverPrivate); + + screensaver_init_dbus (scr); +#ifdef GDK_WINDOWING_X11 + screensaver_init_x11 (scr); +#else +#warning Unimplemented +#endif +} + +void +totem_scrsaver_disable (TotemScrsaver *scr) +{ + g_return_if_fail (TOTEM_SCRSAVER (scr)); + + if (scr->priv->disabled != FALSE) + return; + + scr->priv->disabled = TRUE; + + if (screensaver_is_running_dbus (scr) != FALSE) + screensaver_disable_dbus (scr); + else +#ifdef GDK_WINDOWING_X11 + screensaver_disable_x11 (scr); +#else +#warning Unimplemented + {} +#endif +} + +void +totem_scrsaver_enable (TotemScrsaver *scr) +{ + g_return_if_fail (TOTEM_SCRSAVER (scr)); + + if (scr->priv->disabled == FALSE) + return; + + scr->priv->disabled = FALSE; + + if (screensaver_is_running_dbus (scr) != FALSE) + screensaver_enable_dbus (scr); + else +#ifdef GDK_WINDOWING_X11 + screensaver_enable_x11 (scr); +#else +#warning Unimplemented + {} +#endif +} + +void +totem_scrsaver_set_state (TotemScrsaver *scr, gboolean enable) +{ + g_return_if_fail (TOTEM_SCRSAVER (scr)); + + if (scr->priv->disabled == !enable) + return; + + if (enable == FALSE) + totem_scrsaver_disable (scr); + else + totem_scrsaver_enable (scr); +} + +static void +totem_scrsaver_finalize (GObject *object) +{ + TotemScrsaver *scr = TOTEM_SCRSAVER (object); + + g_free (scr->priv->reason); + + screensaver_finalize_dbus (scr); +#ifdef GDK_WINDOWING_X11 + screensaver_finalize_x11 (scr); +#else +#warning Unimplemented + {} +#endif + + G_OBJECT_CLASS (totem_scrsaver_parent_class)->finalize (object); +} diff --git a/cut-n-paste/totem-screensaver/totem-scrsaver.h b/cut-n-paste/totem-screensaver/totem-scrsaver.h new file mode 100644 index 00000000..bb95174d --- /dev/null +++ b/cut-n-paste/totem-screensaver/totem-scrsaver.h @@ -0,0 +1,57 @@ +/* + Copyright (C) 2004, Bastien Nocera + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301 USA. + + Author: Bastien Nocera + */ + +#ifndef TOTEM_SCRSAVER_H +#define TOTEM_SCRSAVER_H + +#include + +G_BEGIN_DECLS + +#define TOTEM_TYPE_SCRSAVER (totem_scrsaver_get_type ()) +#define TOTEM_SCRSAVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TOTEM_TYPE_SCRSAVER, TotemScrsaver)) +#define TOTEM_SCRSAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TOTEM_TYPE_SCRSAVER, TotemScrsaverClass)) +#define TOTEM_IS_SCRSAVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TOTEM_TYPE_SCRSAVER)) +#define TOTEM_IS_SCRSAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TOTEM_TYPE_SCRSAVER)) + +typedef struct TotemScrsaver TotemScrsaver; +typedef struct TotemScrsaverClass TotemScrsaverClass; +typedef struct TotemScrsaverPrivate TotemScrsaverPrivate; + +struct TotemScrsaver { + GObject parent; + TotemScrsaverPrivate *priv; +}; + +struct TotemScrsaverClass { + GObjectClass parent_class; +}; + +GType totem_scrsaver_get_type (void) G_GNUC_CONST; +TotemScrsaver *totem_scrsaver_new (void); +void totem_scrsaver_enable (TotemScrsaver *scr); +void totem_scrsaver_disable (TotemScrsaver *scr); +void totem_scrsaver_set_state (TotemScrsaver *scr, + gboolean enable); + +G_END_DECLS + +#endif /* !TOTEM_SCRSAVER_H */ diff --git a/cut-n-paste/zoom-control/Makefile.am b/cut-n-paste/zoom-control/Makefile.am new file mode 100644 index 00000000..af8bd48e --- /dev/null +++ b/cut-n-paste/zoom-control/Makefile.am @@ -0,0 +1,16 @@ +noinst_LTLIBRARIES = libephyzoom.la + +libephyzoom_la_SOURCES = \ + ephy-zoom-action.h \ + ephy-zoom-action.c \ + ephy-zoom-control.c \ + ephy-zoom-control.h \ + ephy-zoom.c \ + ephy-zoom.h + +libephyzoom_la_CFLAGS = \ + $(SHELL_CORE_CFLAGS) \ + $(WARNING_CFLAGS) \ + $(DISABLE_DEPRECATED) + +-include $(top_srcdir)/git.mk diff --git a/cut-n-paste/zoom-control/ephy-zoom-action.c b/cut-n-paste/zoom-control/ephy-zoom-action.c new file mode 100644 index 00000000..b6250c8b --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom-action.c @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2003 Marco Pesenti Gritti + * Copyright (C) 2003, 2004 Christian Persch + * + * Modified 2005 by James Bowes for use in evince. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "ephy-zoom-action.h" +#include "ephy-zoom-control.h" +#include "ephy-zoom.h" + +#include +#include +#include + +#define EPHY_ZOOM_ACTION_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_ZOOM_ACTION, EphyZoomActionPrivate)) + +struct _EphyZoomActionPrivate +{ + float zoom; + float min_zoom; + float max_zoom; +}; + +enum +{ + PROP_0, + PROP_ZOOM, + PROP_MIN_ZOOM, + PROP_MAX_ZOOM +}; + + +static void ephy_zoom_action_init (EphyZoomAction *action); +static void ephy_zoom_action_class_init (EphyZoomActionClass *class); + +enum +{ + ZOOM_TO_LEVEL_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (EphyZoomAction, ephy_zoom_action, GTK_TYPE_ACTION) + +static void +zoom_to_level_cb (EphyZoomControl *control, + float zoom, + EphyZoomAction *action) +{ + g_signal_emit (action, signals[ZOOM_TO_LEVEL_SIGNAL], 0, zoom); +} + +static void +sync_zoom_cb (GtkAction *action, GParamSpec *pspec, GtkWidget *proxy) +{ + EphyZoomAction *zoom_action = EPHY_ZOOM_ACTION (action); + + g_object_set (G_OBJECT (proxy), "zoom", zoom_action->priv->zoom, NULL); +} + +static void +sync_min_zoom_cb (GtkAction *action, GParamSpec *pspec, GtkWidget *proxy) +{ + EphyZoomAction *zoom_action = EPHY_ZOOM_ACTION (action); + + g_object_set (G_OBJECT (proxy), "min-zoom", zoom_action->priv->min_zoom, NULL); +} + +static void +sync_max_zoom_cb (GtkAction *action, GParamSpec *pspec, GtkWidget *proxy) +{ + EphyZoomAction *zoom_action = EPHY_ZOOM_ACTION (action); + + g_object_set (G_OBJECT (proxy), "max-zoom", zoom_action->priv->max_zoom, NULL); +} + +static void +connect_proxy (GtkAction *action, GtkWidget *proxy) +{ + if (EPHY_IS_ZOOM_CONTROL (proxy)) + { + g_signal_connect_object (action, "notify::zoom", + G_CALLBACK (sync_zoom_cb), proxy, 0); + g_signal_connect_object (action, "notify::min-zoom", + G_CALLBACK (sync_min_zoom_cb), proxy, 0); + g_signal_connect_object (action, "notify::max-zoom", + G_CALLBACK (sync_max_zoom_cb), proxy, 0); + g_signal_connect (proxy, "zoom_to_level", + G_CALLBACK (zoom_to_level_cb), action); + } + + GTK_ACTION_CLASS (ephy_zoom_action_parent_class)->connect_proxy (action, proxy); +} + +static void +proxy_menu_activate_cb (GtkMenuItem *menu_item, EphyZoomAction *action) +{ + gint index; + float zoom; + + /* menu item was toggled OFF */ + if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (menu_item))) return; + + index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "zoom-level")); + zoom = zoom_levels[index].level; + + if (zoom != action->priv->zoom) + { + g_signal_emit (action, signals[ZOOM_TO_LEVEL_SIGNAL], 0, zoom); + } +} + +static GtkWidget * +create_menu_item (GtkAction *action) +{ + EphyZoomActionPrivate *p = EPHY_ZOOM_ACTION (action)->priv; + GtkWidget *menu, *menu_item; + GSList *group = NULL; + int i; + + menu = gtk_menu_new (); + + for (i = 0; i < n_zoom_levels; i++) + { + if (zoom_levels[i].level == EPHY_ZOOM_SEPARATOR) + { + menu_item = gtk_separator_menu_item_new (); + } + else + { + menu_item = gtk_radio_menu_item_new_with_label (group, + _(zoom_levels[i].name)); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menu_item)); + + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), + p->zoom == zoom_levels[i].level); + + g_object_set_data (G_OBJECT (menu_item), "zoom-level", GINT_TO_POINTER (i)); + g_signal_connect_object (G_OBJECT (menu_item), "activate", + G_CALLBACK (proxy_menu_activate_cb), action, 0); + } + + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item); + } + + gtk_widget_show (menu); + + menu_item = GTK_ACTION_CLASS (ephy_zoom_action_parent_class)->create_menu_item (action); + + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu); + + gtk_widget_show (menu_item); + + return menu_item; +} + +static void +ephy_zoom_action_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EphyZoomAction *action; + + action = EPHY_ZOOM_ACTION (object); + + switch (prop_id) + { + case PROP_ZOOM: + action->priv->zoom = g_value_get_float (value); + break; + case PROP_MIN_ZOOM: + action->priv->min_zoom = g_value_get_float (value); + break; + case PROP_MAX_ZOOM: + action->priv->max_zoom = g_value_get_float (value); + break; + } +} + +static void +ephy_zoom_action_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EphyZoomAction *action; + + action = EPHY_ZOOM_ACTION (object); + + switch (prop_id) + { + case PROP_ZOOM: + g_value_set_float (value, action->priv->zoom); + break; + case PROP_MIN_ZOOM: + g_value_set_float (value, action->priv->min_zoom); + break; + case PROP_MAX_ZOOM: + g_value_set_float (value, action->priv->max_zoom); + break; + } +} + +static void +ephy_zoom_action_class_init (EphyZoomActionClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkActionClass *action_class = GTK_ACTION_CLASS (class); + + object_class->set_property = ephy_zoom_action_set_property; + object_class->get_property = ephy_zoom_action_get_property; + + action_class->toolbar_item_type = EPHY_TYPE_ZOOM_CONTROL; + action_class->connect_proxy = connect_proxy; + action_class->create_menu_item = create_menu_item; + + g_object_class_install_property (object_class, + PROP_ZOOM, + g_param_spec_float ("zoom", + "Zoom", + "Zoom", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + 1.0, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_MIN_ZOOM, + g_param_spec_float ("min-zoom", + "MinZoom", + "The minimum zoom", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + ZOOM_MINIMAL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_MAX_ZOOM, + g_param_spec_float ("max-zoom", + "MaxZoom", + "The maximum zoom", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + ZOOM_MAXIMAL, + G_PARAM_READWRITE)); + + signals[ZOOM_TO_LEVEL_SIGNAL] = + g_signal_new ("zoom_to_level", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EphyZoomActionClass, zoom_to_level), + NULL, NULL, + g_cclosure_marshal_VOID__FLOAT, + G_TYPE_NONE, + 1, + G_TYPE_FLOAT); + + g_type_class_add_private (object_class, sizeof (EphyZoomActionPrivate)); +} + +static void +ephy_zoom_action_init (EphyZoomAction *action) +{ + action->priv = EPHY_ZOOM_ACTION_GET_PRIVATE (action); + + action->priv->zoom = 1.0; +} + +void +ephy_zoom_action_set_zoom_level (EphyZoomAction *action, float zoom) +{ + g_return_if_fail (EPHY_IS_ZOOM_ACTION (action)); + + if (zoom < ZOOM_MINIMAL || zoom > ZOOM_MAXIMAL) return; + + action->priv->zoom = zoom; + g_object_notify (G_OBJECT (action), "zoom"); +} + +float +ephy_zoom_action_get_zoom_level (EphyZoomAction *action) +{ + g_return_val_if_fail (EPHY_IS_ZOOM_ACTION (action), 1.0); + + return action->priv->zoom; +} + +void +ephy_zoom_action_set_min_zoom_level (EphyZoomAction *action, + float zoom) +{ + g_return_if_fail (EPHY_IS_ZOOM_ACTION (action)); + + if (zoom < ZOOM_MINIMAL || zoom > ZOOM_MAXIMAL) return; + + action->priv->min_zoom = zoom; + if (action->priv->zoom > 0 && action->priv->zoom < zoom) + ephy_zoom_action_set_zoom_level (action, zoom); + + g_object_notify (G_OBJECT (action), "min-zoom"); +} + +void +ephy_zoom_action_set_max_zoom_level (EphyZoomAction *action, + float zoom) +{ + g_return_if_fail (EPHY_IS_ZOOM_ACTION (action)); + + if (zoom < ZOOM_MINIMAL || zoom > ZOOM_MAXIMAL) return; + + action->priv->max_zoom = zoom; + if (action->priv->zoom > 0 && action->priv->zoom > zoom) + ephy_zoom_action_set_zoom_level (action, zoom); + + g_object_notify (G_OBJECT (action), "max-zoom"); +} diff --git a/cut-n-paste/zoom-control/ephy-zoom-action.h b/cut-n-paste/zoom-control/ephy-zoom-action.h new file mode 100644 index 00000000..cf9f6feb --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom-action.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003 Marco Pesenti Gritti + * Copyright (C) 2003 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#ifndef EPHY_ZOOM_ACTION_H +#define EPHY_ZOOM_ACTION_H + +#include + +G_BEGIN_DECLS + +#define EPHY_TYPE_ZOOM_ACTION (ephy_zoom_action_get_type ()) +#define EPHY_ZOOM_ACTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EPHY_TYPE_ZOOM_ACTION, EphyZoomAction)) +#define EPHY_ZOOM_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EPHY_TYPE_ZOOM_ACTION, EphyZoomActionClass)) +#define EPHY_IS_ZOOM_ACTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EPHY_TYPE_ZOOM_ACTION)) +#define EPHY_IS_ZOOM_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EPHY_TYPE_ZOOM_ACTION)) +#define EPHY_ZOOM_ACTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_ZOOM_ACTION, EphyZoomActionClass)) + +typedef struct _EphyZoomAction EphyZoomAction; +typedef struct _EphyZoomActionClass EphyZoomActionClass; +typedef struct _EphyZoomActionPrivate EphyZoomActionPrivate; + +struct _EphyZoomAction +{ + GtkAction parent; + + /*< private >*/ + EphyZoomActionPrivate *priv; +}; + +struct _EphyZoomActionClass +{ + GtkActionClass parent_class; + + void (* zoom_to_level) (EphyZoomAction *action, float level); +}; + +GType ephy_zoom_action_get_type (void) G_GNUC_CONST; + +void ephy_zoom_action_set_zoom_level (EphyZoomAction *action, + float zoom); +float ephy_zoom_action_get_zoom_level (EphyZoomAction *action); + +void ephy_zoom_action_set_min_zoom_level (EphyZoomAction *action, + float zoom); +void ephy_zoom_action_set_max_zoom_level (EphyZoomAction *action, + float zoom); + +G_END_DECLS + +#endif diff --git a/cut-n-paste/zoom-control/ephy-zoom-control.c b/cut-n-paste/zoom-control/ephy-zoom-control.c new file mode 100644 index 00000000..6c86b4a4 --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom-control.c @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2003, 2004 Christian Persch + * + * Modified 2005 by James Bowes for use in evince. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "ephy-zoom-control.h" +#include "ephy-zoom.h" + +#include +#include + +#define EPHY_ZOOM_CONTROL_GET_PRIVATE(object)\ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_ZOOM_CONTROL, EphyZoomControlPrivate)) + +struct _EphyZoomControlPrivate +{ + GtkComboBox *combo; + float zoom; + float min_zoom; + float max_zoom; + guint handler_id; +}; + +enum +{ + COL_TEXT, + COL_IS_SEP +}; + +enum +{ + PROP_0, + PROP_ZOOM, + PROP_MIN_ZOOM, + PROP_MAX_ZOOM +}; + +enum +{ + ZOOM_TO_LEVEL_SIGNAL, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE (EphyZoomControl, ephy_zoom_control, GTK_TYPE_TOOL_ITEM) + +static void +combo_changed_cb (GtkComboBox *combo, EphyZoomControl *control) +{ + gint index; + float zoom; + + index = gtk_combo_box_get_active (combo); + zoom = zoom_levels[index].level; + + if (zoom != control->priv->zoom) + { + g_signal_emit (control, signals[ZOOM_TO_LEVEL_SIGNAL], 0, zoom); + } +} + +static void +sync_zoom_cb (EphyZoomControl *control, GParamSpec *pspec, gpointer data) +{ + EphyZoomControlPrivate *p = control->priv; + guint index; + + index = ephy_zoom_get_zoom_level_index (p->zoom); + + g_signal_handler_block (p->combo, p->handler_id); + gtk_combo_box_set_active (p->combo, index); + g_signal_handler_unblock (p->combo, p->handler_id); +} + +static void +sync_zoom_max_min_cb (EphyZoomControl *control, GParamSpec *pspec, gpointer data) +{ + EphyZoomControlPrivate *p = control->priv; + GtkListStore *model = (GtkListStore *)gtk_combo_box_get_model (p->combo); + GtkTreeIter iter; + gint i; + + g_signal_handler_block (p->combo, p->handler_id); + gtk_list_store_clear (model); + + for (i = 0; i < n_zoom_levels; i++) + { + if (zoom_levels[i].level > 0) { + if (zoom_levels[i].level < p->min_zoom) + continue; + + if (zoom_levels[i].level > p->max_zoom) + break; + } + + gtk_list_store_append (model, &iter); + + if (zoom_levels[i].name != NULL) { + gtk_list_store_set (model, &iter, + COL_TEXT, _(zoom_levels[i].name), + -1); + } else { + gtk_list_store_set (model, &iter, + COL_IS_SEP, zoom_levels[i].name == NULL, + -1); + } + } + + gtk_combo_box_set_active (p->combo, ephy_zoom_get_zoom_level_index (p->zoom)); + g_signal_handler_unblock (p->combo, p->handler_id); +} + +static gboolean +row_is_separator (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gboolean is_sep; + gtk_tree_model_get (model, iter, COL_IS_SEP, &is_sep, -1); + return is_sep; +} + +static void +ephy_zoom_control_finalize (GObject *o) +{ + EphyZoomControl *control = EPHY_ZOOM_CONTROL (o); + + g_object_unref (control->priv->combo); + + G_OBJECT_CLASS (ephy_zoom_control_parent_class)->finalize (o); +} + +static void +ephy_zoom_control_init (EphyZoomControl *control) +{ + EphyZoomControlPrivate *p; + GtkWidget *vbox; + GtkCellRenderer *renderer; + GtkListStore *store; + GtkTreeIter iter; + guint i; + + p = EPHY_ZOOM_CONTROL_GET_PRIVATE (control); + control->priv = p; + + p->zoom = 1.0; + + store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + + for (i = 0; i < n_zoom_levels; i++) + { + gtk_list_store_append (store, &iter); + + if (zoom_levels[i].name != NULL) { + gtk_list_store_set (store, &iter, + COL_TEXT, _(zoom_levels[i].name), + -1); + } else { + gtk_list_store_set (store, &iter, + COL_IS_SEP, zoom_levels[i].name == NULL, + -1); + } + } + + p->combo = GTK_COMBO_BOX (gtk_combo_box_new_with_model (GTK_TREE_MODEL (store))); + g_object_unref (store); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (p->combo), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (p->combo), renderer, + "text", COL_TEXT, NULL); + gtk_combo_box_set_row_separator_func (p->combo, + (GtkTreeViewRowSeparatorFunc) row_is_separator, + NULL, NULL); + + gtk_combo_box_set_focus_on_click (p->combo, FALSE); + g_object_ref_sink (G_OBJECT (p->combo)); + gtk_widget_show (GTK_WIDGET (p->combo)); + + i = ephy_zoom_get_zoom_level_index (p->zoom); + gtk_combo_box_set_active (p->combo, i); + + vbox = gtk_vbox_new (TRUE, 0); + gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (p->combo), TRUE, FALSE, 0); + gtk_widget_show (vbox); + + gtk_container_add (GTK_CONTAINER (control), vbox); + + p->handler_id = g_signal_connect (p->combo, "changed", + G_CALLBACK (combo_changed_cb), control); + + g_signal_connect_object (control, "notify::zoom", + G_CALLBACK (sync_zoom_cb), NULL, 0); + g_signal_connect_object (control, "notify::min-zoom", + G_CALLBACK (sync_zoom_max_min_cb), NULL, 0); + g_signal_connect_object (control, "notify::max-zoom", + G_CALLBACK (sync_zoom_max_min_cb), NULL, 0); +} + +static void +ephy_zoom_control_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EphyZoomControl *control; + EphyZoomControlPrivate *p; + + control = EPHY_ZOOM_CONTROL (object); + p = control->priv; + + switch (prop_id) + { + case PROP_ZOOM: + p->zoom = g_value_get_float (value); + break; + case PROP_MIN_ZOOM: + p->min_zoom = g_value_get_float (value); + break; + case PROP_MAX_ZOOM: + p->max_zoom = g_value_get_float (value); + break; + } +} + +static void +ephy_zoom_control_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EphyZoomControl *control; + EphyZoomControlPrivate *p; + + control = EPHY_ZOOM_CONTROL (object); + p = control->priv; + + switch (prop_id) + { + case PROP_ZOOM: + g_value_set_float (value, p->zoom); + break; + case PROP_MIN_ZOOM: + g_value_set_float (value, p->min_zoom); + break; + case PROP_MAX_ZOOM: + g_value_set_float (value, p->max_zoom); + break; + } +} + +static void +ephy_zoom_control_class_init (EphyZoomControlClass *klass) +{ + GObjectClass *object_class; + GtkToolItemClass *tool_item_class; + + object_class = (GObjectClass *)klass; + tool_item_class = (GtkToolItemClass *)klass; + + object_class->set_property = ephy_zoom_control_set_property; + object_class->get_property = ephy_zoom_control_get_property; + object_class->finalize = ephy_zoom_control_finalize; + + g_object_class_install_property (object_class, + PROP_ZOOM, + g_param_spec_float ("zoom", + "Zoom", + "Zoom level to display in the item.", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + 1.0, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_MIN_ZOOM, + g_param_spec_float ("min-zoom", + "MinZoom", + "The minimum zoom", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + ZOOM_MINIMAL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_MAX_ZOOM, + g_param_spec_float ("max-zoom", + "MaxZoom", + "The maximum zoom", + ZOOM_MINIMAL, + ZOOM_MAXIMAL, + ZOOM_MAXIMAL, + G_PARAM_READWRITE)); + + signals[ZOOM_TO_LEVEL_SIGNAL] = + g_signal_new ("zoom_to_level", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EphyZoomControlClass, + zoom_to_level), + NULL, NULL, + g_cclosure_marshal_VOID__FLOAT, + G_TYPE_NONE, + 1, + G_TYPE_FLOAT); + + g_type_class_add_private (object_class, sizeof (EphyZoomControlPrivate)); +} + +void +ephy_zoom_control_set_zoom_level (EphyZoomControl *control, float zoom) +{ + g_return_if_fail (EPHY_IS_ZOOM_CONTROL (control)); + + if (zoom < ZOOM_MINIMAL || zoom > ZOOM_MAXIMAL) return; + + control->priv->zoom = zoom; + g_object_notify (G_OBJECT (control), "zoom"); +} + +float +ephy_zoom_control_get_zoom_level (EphyZoomControl *control) +{ + g_return_val_if_fail (EPHY_IS_ZOOM_CONTROL (control), 1.0); + + return control->priv->zoom; +} diff --git a/cut-n-paste/zoom-control/ephy-zoom-control.h b/cut-n-paste/zoom-control/ephy-zoom-control.h new file mode 100644 index 00000000..8e74e7b1 --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom-control.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#ifndef EPHY_ZOOM_CONTROL_H +#define EPHY_ZOOM_CONTROL_H + +#include + +G_BEGIN_DECLS + +#define EPHY_TYPE_ZOOM_CONTROL (ephy_zoom_control_get_type()) +#define EPHY_ZOOM_CONTROL(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_ZOOM_CONTROL, EphyZoomControl)) +#define EPHY_ZOOM_CONTROL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_ZOOM_CONTROL, EphyZoomControlClass)) +#define EPHY_IS_ZOOM_CONTROL(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_ZOOM_CONTROL)) +#define EPHY_IS_ZOOM_CONTROL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_ZOOM_CONTROL)) +#define EPHY_ZOOM_CONTROL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_ZOOM_CONTROL, EphyZoomControlClass)) + +typedef struct _EphyZoomControl EphyZoomControl; +typedef struct _EphyZoomControlClass EphyZoomControlClass; +typedef struct _EphyZoomControlPrivate EphyZoomControlPrivate; + +struct _EphyZoomControlClass +{ + GtkToolItemClass parent_class; + + /* signals */ + void (*zoom_to_level) (EphyZoomControl *control, float level); +}; + +struct _EphyZoomControl +{ + GtkToolItem parent_object; + + /*< private >*/ + EphyZoomControlPrivate *priv; +}; + +GType ephy_zoom_control_get_type (void); + +void ephy_zoom_control_set_zoom_level (EphyZoomControl *control, float zoom); + +float ephy_zoom_control_get_zoom_level (EphyZoomControl *control); + +G_END_DECLS + +#endif diff --git a/cut-n-paste/zoom-control/ephy-zoom.c b/cut-n-paste/zoom-control/ephy-zoom.c new file mode 100644 index 00000000..83bb8291 --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003 Christian Persch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#include "config.h" + +#include "ephy-zoom.h" + +#include + +guint +ephy_zoom_get_zoom_level_index (float level) +{ + guint i; + float previous, current, mean; + + /* Handle our options at the beginning of the list. */ + if (level == EPHY_ZOOM_BEST_FIT) { + return 0; + } else if (level == EPHY_ZOOM_FIT_WIDTH) { + return 1; + } + + previous = zoom_levels[3].level; + + for (i = 4; i < n_zoom_levels; i++) + { + current = zoom_levels[i].level; + mean = sqrt (previous * current); + + if (level <= mean) return i - 1; + + previous = current; + } + + return n_zoom_levels - 1; +} + + +float +ephy_zoom_get_changed_zoom_level (float level, gint steps) +{ + guint index; + + index = ephy_zoom_get_zoom_level_index (level); + return zoom_levels[CLAMP(index + steps, 3, n_zoom_levels - 1)].level; +} + +float ephy_zoom_get_nearest_zoom_level (float level) +{ + return ephy_zoom_get_changed_zoom_level (level, 0); +} diff --git a/cut-n-paste/zoom-control/ephy-zoom.h b/cut-n-paste/zoom-control/ephy-zoom.h new file mode 100644 index 00000000..bf01f0d7 --- /dev/null +++ b/cut-n-paste/zoom-control/ephy-zoom.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2003 Christian Persch + * + * Modified 2005 by James Bowes for use in evince. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $Id$ + */ + +#ifndef EPHY_ZOOM_H +#define EPHY_ZOOM_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +G_BEGIN_DECLS + +#define EPHY_ZOOM_BEST_FIT (-3.0) +#define EPHY_ZOOM_FIT_WIDTH (-4.0) +#define EPHY_ZOOM_SEPARATOR (-5.0) + +static const +struct +{ + gchar *name; + float level; +} + +zoom_levels[] = +{ + { N_("Best Fit"), EPHY_ZOOM_BEST_FIT }, + { N_("Fit Page Width"), EPHY_ZOOM_FIT_WIDTH }, + { NULL, EPHY_ZOOM_SEPARATOR }, + { N_("50%"), 0.5 }, + { N_("70%"), 0.7071067811 }, + { N_("85%"), 0.8408964152 }, + { N_("100%"), 1.0 }, + { N_("125%"), 1.1892071149 }, + { N_("150%"), 1.4142135623 }, + { N_("175%"), 1.6817928304 }, + { N_("200%"), 2.0 }, + { N_("300%"), 2.8284271247 }, + { N_("400%"), 4.0 }, + { N_("800%"), 8.0 }, + { N_("1600%"), 16.0 }, + { N_("3200%"), 32.0 }, + { N_("6400%"), 64.0 } +}; +static const guint n_zoom_levels = G_N_ELEMENTS (zoom_levels); + +#define ZOOM_MINIMAL (EPHY_ZOOM_SEPARATOR) +#define ZOOM_MAXIMAL (zoom_levels[n_zoom_levels - 1].level) +#define ZOOM_IN (-1.0) +#define ZOOM_OUT (-2.0) + +guint ephy_zoom_get_zoom_level_index (float level); + +float ephy_zoom_get_changed_zoom_level (float level, gint steps); + +float ephy_zoom_get_nearest_zoom_level (float level); + +G_END_DECLS + +#endif diff --git a/data/Makefile.am b/data/Makefile.am new file mode 100644 index 00000000..f80e9c6b --- /dev/null +++ b/data/Makefile.am @@ -0,0 +1,109 @@ +SUBDIRS = icons +NULL= + +install-data-local: update-icon-cache + +# +# man file +# + +man_MANS=evince.1 + +# +# UI descriptions +# + +uidir = $(pkgdatadir) +ui_DATA = \ + evince-ui.xml \ + evince-toolbar.xml \ + hand-open.png + +if ENABLE_PREVIEWER +ui_DATA += evince-previewer-ui.xml +endif + +# +# Desktop file +# + +@INTLTOOL_DESKTOP_RULE@ + +DESKTOP_IN_FILES= evince.desktop.in.in +DESKTOP_FILES= $(DESKTOP_IN_FILES:.desktop.in.in=.desktop) + +desktopdir = $(datadir)/applications +desktop_DATA = $(DESKTOP_FILES) + +# +# DBus servide file +# +if ENABLE_DBUS +servicedir = $(datadir)/dbus-1/services +service_in_files = org.mate.evince.Daemon.service.in +service_DATA = $(service_in_files:.service.in=.service) + +$(service_DATA): $(service_in_files) Makefile + $(AM_V_GEN) $(SED) -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@ +endif + +# +# GSettings schema +# +gsettingsschema_in_files = org.mate.Evince.gschema.xml.in +# gsettings_SCHEMAS is a list of all the schemas you want to install +gsettings_SCHEMAS = $(gsettingsschema_in_files:.xml.in=.xml) + +.PRECIOUS: $(gsettings_SCHEMAS) + +@INTLTOOL_XML_NOMERGE_RULE@ + +# include the appropriate makefile rules for schema handling +@GSETTINGS_RULES@ + +gsettingsconvertdir = $(datadir)/MateConf/gsettings +gsettingsconvert_DATA = evince.convert + +# +# GTK icon cache +# + +gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datadir)/icons/hicolor + +install-data-hook: update-icon-cache +uninstall-hook: update-icon-cache + +update-icon-cache: + if test -z "$(DESTDIR)"; then \ + echo "Updating Gtk icon cache."; \ + $(gtk_update_icon_cache); \ + else \ + echo "*** Icon cache not updated. After (un)install, run this:"; \ + echo "*** $(gtk_update_icon_cache)"; \ + fi + + +# +# Extra files to be included in the tarball +# + +EXTRA_DIST = \ + $(ui_DATA) \ + $(DESKTOP_IN_FILES) \ + $(gsettingsschema_in_files) \ + org.mate.evince.Daemon.service.in \ + $(man_MANS) \ + evince.ico \ + evince.convert \ + $(NULL) + +# +# Clean up properly +# + +DISTCLEANFILES = \ + $(DESKTOP_FILES) \ + $(gsettings_SCHEMAS) \ + $(service_DATA) + +-include $(top_srcdir)/git.mk diff --git a/data/evince-previewer-ui.xml b/data/evince-previewer-ui.xml new file mode 100644 index 00000000..896b472e --- /dev/null +++ b/data/evince-previewer-ui.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/evince-toolbar.xml b/data/evince-toolbar.xml new file mode 100644 index 00000000..b87bf717 --- /dev/null +++ b/data/evince-toolbar.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/evince-ui.xml b/data/evince-ui.xml new file mode 100644 index 00000000..f8e750db --- /dev/null +++ b/data/evince-ui.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/evince.1 b/data/evince.1 new file mode 100644 index 00000000..43dd768b --- /dev/null +++ b/data/evince.1 @@ -0,0 +1,41 @@ +.TH EVINCE 1 "30 Jan 2007" +.SH NAME +\fBevince\fP \- MATE document viewer + +The Evince Document Viewer application for MATE desktop environment + +.SH SYNTAX +.B evince +.RI [--help] +.RI [--usage] +.RI [--page-label=