The Birdfont Source Code


All Repositories / birdfont.git / commitdiff – RSS feed

Merge ../birdfont-2.x

These changes was commited to the Birdfont repository Sun, 08 Jan 2017 12:55:27 +0000.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git
[Sun, 08 Jan 2017 12:55:27 +0000]

Updated Files

README.md
birdfont-test/TestRunner.vala
birdfont/GtkWindow.vala
birdfont/Main.vala
build.py
configure
dodo.py
install.py
libbirdfont/BackgroundTab.vala
libbirdfont/BackgroundTools.vala
libbirdfont/BezierPoints.vala
libbirdfont/BezierTool.vala
libbirdfont/BirdFont.vala
libbirdfont/BirdFontFile.vala
libbirdfont/CanvasSettings.vala
libbirdfont/CircleTool.vala
libbirdfont/ClipTool.vala
libbirdfont/Color.vala
libbirdfont/ColorPicker.vala
libbirdfont/DrawingTools.vala
libbirdfont/EmbeddedSvg.vala
libbirdfont/Expander.vala
libbirdfont/ExportTool.vala
libbirdfont/FileDialogTab.vala
libbirdfont/Font.vala
libbirdfont/Glyph.vala
libbirdfont/GlyphCanvas.vala
libbirdfont/GlyphRange.vala
libbirdfont/GlyphSequence.vala
libbirdfont/Gradient.vala
libbirdfont/ImportUtils.vala
libbirdfont/KerningDisplay.vala
libbirdfont/LabelTool.vala
libbirdfont/Layer.vala
libbirdfont/LayerLabel.vala
libbirdfont/LayerUtils.vala
libbirdfont/LicenseDialog.vala
libbirdfont/Ligatures.vala
libbirdfont/Line.vala
libbirdfont/MainWindow.vala
libbirdfont/Menu.vala
libbirdfont/MenuItem.vala
libbirdfont/MenuTab.vala
libbirdfont/MoveTool.vala
libbirdfont/OpenFontFormat/ContextualLigature.vala
libbirdfont/OpenFontFormat/DirectoryTable.vala
libbirdfont/OpenFontFormat/FontData.vala
libbirdfont/OpenFontFormat/GlyfData.vala
libbirdfont/OpenFontFormat/GlyfTable.vala
libbirdfont/OpenFontFormat/HheaTable.vala
libbirdfont/OpenFontFormat/OpenFontFormatReader.vala
libbirdfont/OpenFontFormat/OpenFontFormatWriter.vala
libbirdfont/OpenFontFormat/OtfInputStream.vala
libbirdfont/OpenFontFormat/OtfTable.vala
libbirdfont/OpenFontFormat/PostTable.vala
libbirdfont/OpenFontFormat/SvgTable.vala
libbirdfont/OpenFontFormat/SvgTableEntry.vala
libbirdfont/OrientationTool.vala
libbirdfont/OtfFeatureTable.vala
libbirdfont/OverView.vala
libbirdfont/OverViewItem.vala
libbirdfont/OverviewTools.vala
libbirdfont/Path.vala
libbirdfont/PathList.vala
libbirdfont/PathObject.vala
libbirdfont/PenTool.vala
libbirdfont/QuestionDialog.vala
libbirdfont/Renderer/CachedFont.vala
libbirdfont/Renderer/Drawing.vala
libbirdfont/Renderer/FallbackFont.vala
libbirdfont/Renderer/FontCache.vala
libbirdfont/Renderer/LineTextArea.vala
libbirdfont/Renderer/Text.vala
libbirdfont/Renderer/TextArea.vala
libbirdfont/Renderer/fontconfig.c
libbirdfont/ResizeTool.vala
libbirdfont/SaveCallback.vala
libbirdfont/SearchPaths.vala
libbirdfont/SettingsTab.vala
libbirdfont/SpinButton.vala
libbirdfont/Stop.vala
libbirdfont/StrokeTool.vala
libbirdfont/Svg.vala
libbirdfont/SvgArc.vala
libbirdfont/SvgFont.vala
libbirdfont/SvgFontFormatWriter.vala
libbirdfont/SvgParser.vala
libbirdfont/SvgStyle.vala
libbirdfont/TabBar.vala
libbirdfont/TabContent.vala
libbirdfont/Task.vala
libbirdfont/Test.vala
libbirdfont/TestCases.vala
libbirdfont/TextRendering/CachedFont.vala
libbirdfont/TextRendering/Drawing.vala
libbirdfont/TextRendering/FallbackFont.vala
libbirdfont/TextRendering/FontCache.vala
libbirdfont/TextRendering/LineTextArea.vala
libbirdfont/TextRendering/Text.vala
libbirdfont/TextRendering/TextArea.vala
libbirdfont/TextRendering/fontconfig.c
libbirdfont/Theme.vala
libbirdfont/Tool.vala
libbirdfont/ToolItem.vala
libbirdfont/TrackTool.vala
libbirdfont/TransformTask.vala
libbirdgems/fit_cubic.c
libsvgbird/AttributePattern.vala
libsvgbird/BezierPoints.vala
libsvgbird/Circle.vala
libsvgbird/ClipPath.vala
libsvgbird/Color.vala
libsvgbird/Defs.vala
libsvgbird/Doubles.vala
libsvgbird/Ellipse.vala
libsvgbird/EmptyObject.vala
libsvgbird/Gradient.vala
libsvgbird/Layer.vala
libsvgbird/Line.vala
libsvgbird/LineCap.vala
libsvgbird/LinearGradient.vala
libsvgbird/Object.vala
libsvgbird/ObjectGroup.vala
libsvgbird/PointValue.vala
libsvgbird/Points.vala
libsvgbird/Polygon.vala
libsvgbird/Polyline.vala
libsvgbird/RadialGradient.vala
libsvgbird/Rectangle.vala
libsvgbird/Selector.vala
libsvgbird/SelectorPattern.vala
libsvgbird/SelectorTag.vala
libsvgbird/Stop.vala
libsvgbird/StyleSheet.vala
libsvgbird/SvgArc.vala
libsvgbird/SvgDrawing.vala
libsvgbird/SvgFile.vala
libsvgbird/SvgPath.vala
libsvgbird/SvgStyle.vala
libsvgbird/SvgTransform.vala
libsvgbird/SvgTransforms.vala
libsvgbird/Text.vala
libsvgbird/ViewBox.vala
libsvgbird/point_value.h
libsvgbird/text_drawing.c
po/birdfont.pot
po/cs.po
po/de.po
po/el.po
po/es.po
po/fi.po
po/fr.po
po/he.po
po/id.po
po/it.po
po/nb.po
po/nl.po
po/oc.po
po/pl.po
po/pt.po
po/pt_BR.po
po/ru.po
po/sk.po
po/sr.po
po/sv.po
po/tr.po
po/uk.po
resources/dark.theme
resources/key_bindings.xml
resources/linux/birdfont-import.1
scripts/builder.py
scripts/version.py
svgbird.deps
svgbirdpoint.vapi
--- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ Author: Johan Mattsson and others see AUTHORS for full attribution. License: GNU GPL v3 - Webpage: http://birdfont.org - Bugtracker: http://birdfont.org/bugtracker/my_view_page.php + Webpage: https://birdfont.org + Bugtracker: https://birdfont.org/bugtracker/my_view_page.php ## Building from Source @@ -24,8 +24,9 @@ libwebkit2gtk-3.0-dev libnotify-dev libsqlite3-dev + libxmlbird-dev - XML Bird is available from [birdfont.org][xmlbird]. + XML Bird is available from [https://birdfont.org][xmlbird]. BirdFont have two build systems, one python script that builds all binaries at once and one dependency based build system that uses @@ -56,10 +57,10 @@ ## Packages Windows and Mac binaries can be downloaded from - http://birdfont.org Many Linux distributions have packages of + https://birdfont.org Many Linux distributions have packages of Birdfont in their repositories. There is a BSD package in OpenBSD. - [birdfont]: http://birdfont.org/images/birdfont_logo2.png "Birdfont logo" - [xmlbird]: http://birdfont.org/xmlbird.php "XML Bird – XML Parser for programs written in VALA" + [birdfont]: https://birdfont.org/images/birdfont_logo2.png "Birdfont logo" + [xmlbird]: https://birdfont.org/xmlbird.php "XML Bird – XML Parser for programs written in VALA"
--- a/birdfont-test/TestRunner.vala +++ b/birdfont-test/TestRunner.vala @@ -37,7 +37,7 @@ if (type == "SVG") { File f = File.new_for_path (file); Font font = new Font (); - import_svg_file (font, f); + import_svg_file (font, f, SvgType.REGULAR); } else if (type == "BF") { Font font = new Font (); font.set_font_file (file);
--- a/birdfont/GtkWindow.vala +++ b/birdfont/GtkWindow.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012s 2013 2014 Johan Mattsson + Copyright (C) 2012 2013 2014 2015 2016 Johan Mattsson 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 @@ -80,7 +80,7 @@ if (fd.get_name () == "Preview") { uri = Preview.get_uri (); html = Preview.get_html_with_absolute_paths (); - + try { html_canvas.load_html (html, uri); } catch (Error e) { @@ -400,8 +400,8 @@ } catch (GLib.Error e) { warning (e.message); } - } - + } + public void run_non_blocking_background_thread (Task t) { unowned Thread<void*> bg; @@ -479,9 +479,9 @@ thread = Thread.create<void*> (this.saving_thread, true); } catch (GLib.Error e) { warning (e.message); - } + } } - + public void* saving_thread () { BirdFont.get_current_font ().save (); MenuTab.stop_background_thread ();
--- a/birdfont/Main.vala +++ b/birdfont/Main.vala @@ -31,10 +31,10 @@ window.set_native (native_window); native_window.init (); - birdfont.load_font_from_command_line (); + BirdFont.BirdFont.load_font_from_command_line (); Gtk.main (); return 0; }
diff --git a/build.py b/build.py
--- a/build.py +++ b/build.py @@ -9,20 +9,30 @@ from scripts import version if platform == 'msys': + process_tasks(dodo.make_libbirdgems('libsvgbird.dll', [])) + process_tasks(dodo.task_svgbird_vapi()) process_tasks(dodo.make_libbirdgems('libbirdgems.dll', [])) - process_tasks(dodo.make_libbirdfont('libbirdfont.dll', ['libbirdgems.dll'])) - process_tasks(dodo.make_libbirdfont('libbirdfont.dll', ['libbirdgems.dll'])) - process_tasks(dodo.make_birdfont_test('birdfont-test.exe', ['libbirdgems.so', 'libbirdfont.so'])) + process_tasks(dodo.make_libbirdfont('libbirdfont.dll', ['libbirdgems.dll', 'libsvgbird.dll'])) + process_tasks(dodo.make_libbirdfont('libbirdfont.dll', ['libbirdgems.dll', 'libsvgbird.dll'])) + process_tasks(dodo.make_birdfont_test('birdfont-test.exe', + ['libbirdgems.dll', 'libbirdfont.dll', 'libsvgbird.dll'])) elif platform == 'darwin': gems = "libbirdgems." + str(version.LIBBIRDGEMS_SO_VERSION) + '.dylib' bird = "libbirdfont." + str(version.SO_VERSION) + '.dylib'; + svg = "libsvgbird." + str(version.LIBSVGBIRD_SO_VERSION) + '.dylib'; + process_tasks(dodo.make_libsvgbird(svg, [])) + process_tasks(dodo.task_svgbird_vapi()) process_tasks(dodo.make_libbirdgems(gems, [])) process_tasks(dodo.make_libbirdfont(bird, [gems])) process_tasks(dodo.task_man()) else: + process_tasks(dodo.task_libsvgbird()) + process_tasks(dodo.task_svgbird_vapi()) + process_tasks(dodo.task_svgbird_vapi()) process_tasks(dodo.task_libbirdgems()) process_tasks(dodo.task_libbirdfont()) - process_tasks(dodo.make_birdfont_test('birdfont-test', ['libbirdgems.so', 'libbirdfont.so'])) + process_tasks(dodo.make_birdfont_test('birdfont-test', + ['libsvgbird.so', 'libbirdgems.so', 'libbirdfont.so'])) if config.GTK: process_tasks(dodo.task_birdfont())
--- a/configure +++ b/configure @@ -11,7 +11,8 @@ from scripts.run import run TARGETS = ['libbirdfont', - 'libbirdgems', + 'libbirdgems', + 'libsvgbird', 'birdfont', 'birdfont-autotrace', 'birdfont-export', @@ -49,20 +50,33 @@ version = [int(n) for n in v.split ('.')] return [a,b,c] <= version - def test_library_version (lib): + def test_library_version (lib, required=True, version=None): print ('Looking for library: ' + lib + '\t\t') process = subprocess.Popen ('pkg-config --modversion ' + lib, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + v = process.stdout.readline().decode('utf-8') process.communicate()[0] - return process.returncode == 0 - - def has_posixvala (): - posixvala = test_library_version ('posixvala') - if not posixvala: - print (OKGREEN + 'Glib will be used instead of Posix (libc).' + ENDC) - return 'False' - else: - print (OKGREEN + 'Using posix profile.' + ENDC) - return 'True' + + if not process.returncode == 0: + if required: + print (FAIL + lib + ' not found' + ENDC) + exit (1) + else: + return False + + if version == None: + return True + + installed_version = v.split ('.'); + library_version = version.split ('.'); + + if installed_version < library_version: + if required: + print (FAIL + lib + ' version >= ' + version + ' not found.' + ENDC) + exit (1) + else: + return False + + return True def configure(gtk, libbgee, valac): global gee @@ -82,7 +96,6 @@ 'libsoup-2.4', 'libnotify', 'sqlite3', - 'xmlbird' ] else: libs = [ @@ -90,25 +103,23 @@ 'glib-2.0', 'sqlite3', 'fontconfig', - 'xmlbird' ] + + test_library_version ('xmlbird', True, '1.2.0') for lib in libs: - if not test_library_version (lib): - print (FAIL + 'Can not find ' + lib + ENDC) - exit (1) + test_library_version (lib) if libbgee == 'Any': - if test_library_version ('gee-0.8'): + if test_library_version ('gee-0.8', False): gee = 'gee-0.8' - elif test_library_version ('gee-1.0'): + elif test_library_version ('gee-1.0', False): gee = 'gee-1.0' else: print (FAIL + 'Can not find libgee (version 0.8 or version 1.0).' + ENDC) exit (1) else: if not test_library_version (libbgee): - print (FAIL + 'Can not find lib gee.' + ENDC) exit (1) gee = libbgee; @@ -187,13 +198,13 @@ configfile.write_config(options.prefix) configfile.write_compile_parameters(options.prefix, - options.dest, - options.cc, - gee, - options.valac, - options.nonnull, - valacflags, - cflags, - ldflags, - options.gtk) + options.dest, + options.cc, + gee, + options.valac, + options.nonnull, + valacflags, + cflags, + ldflags, + options.gtk)
diff --git a/dodo.py b/dodo.py
--- a/dodo.py +++ b/dodo.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2012 2013 2014 2015 Eduardo Naufel Schettino and Johan Mattsson + Copyright (C) 2012 - 2016 Eduardo Naufel Schettino and Johan Mattsson 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 @@ -32,6 +32,8 @@ 'man', 'libbirdfont', 'libbirdgems', + 'libsvgbird', + 'svgbird_vapi', 'birdfont', 'birdfont-autotrace', 'birdfont-export', @@ -46,6 +48,13 @@ LIBBIRDGEMS_SO_VERSION='${LIBbirdgems_VERSION}' else: LIBBIRDGEMS_SO_VERSION=version.LIBBIRDGEMS_SO_VERSION + + if "kfreebsd" in sys.platform: + LIBSVGBIRD_SO_VERSION=version.LIBSVGBIRD_SO_VERSION + elif "openbsd" in sys.platform: + LIBSVGBIRD_SO_VERSION='${LIBbirdgems_VERSION}' + else: + LIBSVGBIRD_SO_VERSION=version.LIBSVGBIRD_SO_VERSION if "kfreebsd" in sys.platform: SO_VERSION=version.SO_VERSION @@ -78,13 +87,15 @@ --pkg webkit2gtk-4.0 \ --pkg libnotify \ --pkg xmlbird \ - --pkg libbirdfont + --pkg libbirdfont \ + --pkg svgbird \ """ cc_command = config.CC + " " + config.CFLAGS.get("birdfont", "") + """ \ -c C_SOURCE \ -D 'GETTEXT_PACKAGE="birdfont"' \ -I./build/libbirdfont \ + -I./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags """ + config.GEE + """) \ $(pkg-config --cflags gio-2.0) \ @@ -107,7 +118,7 @@ $(pkg-config --libs webkit2gtk-4.0) \ $(pkg-config --libs xmlbird) \ $(pkg-config --libs libnotify) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird \ -o build/bin/""" + target_binary birdfont = Builder('birdfont', @@ -121,7 +132,7 @@ yield birdfont.build() def task_birdfont(): - yield make_birdfont('birdfont', ['libbirdgems.so', 'libbirdfont.so']) + yield make_birdfont('birdfont', ['libbirdgems.so', 'libbirdfont.so', 'libsvgbird.so']) def make_birdfont_export(target_binary, deps): valac_command = config.VALAC + """ \ @@ -136,13 +147,16 @@ --pkg gio-2.0 \ --pkg cairo \ --pkg xmlbird \ - --pkg libbirdfont + --pkg svgbird \ + --pkg libbirdfont \ + --pkg svgbird \ """ cc_command = config.CC + " " + config.CFLAGS.get("birdfont-export", "") + """ \ -c C_SOURCE \ -D 'GETTEXT_PACKAGE="birdfont"' \ -I./build/libbirdfont \ + -I./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags """ + config.GEE + """) \ $(pkg-config --cflags gio-2.0) \ @@ -160,7 +174,7 @@ $(pkg-config --libs cairo) \ $(pkg-config --libs glib-2.0) \ $(pkg-config --libs xmlbird) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird \ -o ./build/bin/""" + target_binary birdfont_export = Builder('birdfont-export', @@ -174,7 +188,7 @@ yield birdfont_export.build() def task_birdfont_export(): - yield make_birdfont_export('birdfont-export', ['libbirdgems.so', 'libbirdfont.so']) + yield make_birdfont_export('birdfont-export', ['libbirdgems.so', 'libbirdfont.so', 'libsvgbird.so']) def make_birdfont_import(target_binary, deps): valac_command = config.VALAC + """\ @@ -189,13 +203,15 @@ --pkg gio-2.0 \ --pkg cairo \ --pkg xmlbird \ - --pkg libbirdfont + --pkg libbirdfont \ + --pkg svgbird \ """ cc_command = config.CC + " " + config.CFLAGS.get("birdfont-import", "") + """ \ -c C_SOURCE \ -D 'GETTEXT_PACKAGE="birdfont"' \ -I./build/libbirdfont \ + -I./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags """ + config.GEE + """) \ $(pkg-config --cflags gio-2.0) \ @@ -213,7 +229,7 @@ $(pkg-config --libs cairo) \ $(pkg-config --libs glib-2.0) \ $(pkg-config --libs xmlbird) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird \ -o ./build/bin/""" + target_binary birdfont_import = Builder('birdfont-import', @@ -227,7 +243,7 @@ yield birdfont_import.build() def task_birdfont_import(): - yield make_birdfont_import('birdfont-import', ['libbirdgems.so', 'libbirdfont.so']) + yield make_birdfont_import('birdfont-import', ['libbirdgems.so', 'libbirdfont.so', 'libsvgbird.so']) def make_birdfont_autotrace(target_binary, deps): valac_command = config.VALAC + """\ @@ -243,12 +259,14 @@ --pkg cairo \ --pkg xmlbird \ --pkg libbirdfont \ + --pkg svgbird \ """ cc_command = config.CC + " " + config.CFLAGS.get("birdfont-autotrace", "") + """ \ -c C_SOURCE \ -D 'GETTEXT_PACKAGE="birdfont"' \ -I./build/libbirdfont \ + -I./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags """ + config.GEE + """) \ $(pkg-config --cflags gio-2.0) \ @@ -259,6 +277,7 @@ linker_command = config.CC + " " + config.LDFLAGS.get("birdfont-autotrace", "") + """ \ build/birdfont-autotrace/*.o \ -I./build/libbirdfont \ + -I./build/libsvgbird \ -Lbuild/bin/ -lbirdfont \ -lm \ $(pkg-config --libs sqlite3) \ @@ -267,7 +286,7 @@ $(pkg-config --libs cairo) \ $(pkg-config --libs glib-2.0) \ $(pkg-config --libs xmlbird) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird\ -o ./build/bin/""" + target_binary birdfont_autotrace = Builder('birdfont-autotrace', @@ -281,7 +300,7 @@ yield birdfont_autotrace.build() def task_birdfont_autotrace(): - yield make_birdfont_autotrace('birdfont-autotrace', ['libbirdgems.so', 'libbirdfont.so']) + yield make_birdfont_autotrace('birdfont-autotrace', ['libbirdgems.so', 'libbirdfont.so', 'libsvgbird.so']) def make_libbirdfont(target_binary, deps): valac_command = config.VALAC + """\ @@ -295,13 +314,16 @@ -H build/libbirdfont/birdfont.h \ libbirdfont/*.vala \ libbirdfont/OpenFontFormat/*.vala \ - libbirdfont/Renderer/*.vala \ + libbirdfont/TextRendering/*.vala \ + --pkg posix \ --pkg """ + config.GEE + """ \ --pkg gio-2.0 \ --pkg cairo \ --pkg xmlbird \ --pkg libbirdgems \ + --pkg svgbird \ --pkg sqlite3 \ + --pkg gdk-pixbuf-2.0 \ """ cc_command = config.CC + " " + config.CFLAGS.get("libbirdfont", "") + """ \ @@ -310,6 +332,7 @@ -D 'GETTEXT_PACKAGE="birdfont"' \ -I ./build/libbirdfont \ -I ./build/libbirdgems \ + -I ./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags fontconfig) \ $(pkg-config --cflags """ + config.GEE + """) \ @@ -331,7 +354,7 @@ $(pkg-config --libs cairo) \ $(pkg-config --libs glib-2.0) \ $(pkg-config --libs xmlbird) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird \ -o ./build/bin/""" + target_binary libbirdfont = Builder('libbirdfont', @@ -345,8 +368,98 @@ yield libbirdfont.build() def task_libbirdfont(): - yield make_libbirdfont('libbirdfont.so.' + SO_VERSION, ['libbirdgems.so']) + yield make_libbirdfont('libbirdfont.so.' + SO_VERSION, ['libbirdgems.so', 'libsvgbird.so']) + + def make_libsvgbird(target_binary, deps): + global version + + valac_command = config.VALAC + """\ + -C \ + --vapidir=./ \ + --basedir build/libsvgbird/ \ + """ + config.NON_NULL + """ \ + """ + config.VALACFLAGS.get("libsvgbird", "") + """ \ + --enable-experimental \ + --library libsvgbird \ + -H build/libsvgbird/svgbird.h \ + libsvgbird/*.vala \ + --pkg posix \ + --pkg svgbirdpoint \ + --pkg """ + config.GEE + """ \ + --pkg gio-2.0 \ + --pkg cairo \ + --pkg xmlbird \ + --vapi=./svgbird_generated.vapi \ + """ + + cc_command = config.CC + " " + config.CFLAGS.get("libsvgbird", "") + """ \ + -c C_SOURCE \ + -std=c99 \ + -fPIC \ + -I ./build/libsvgbird \ + $(pkg-config --cflags """ + config.GEE + """) \ + $(pkg-config --cflags gio-2.0) \ + $(pkg-config --cflags cairo) \ + $(pkg-config --cflags glib-2.0) \ + $(pkg-config --cflags xmlbird) \ + $(pkg-config --cflags fontconfig) \ + $(pkg-config --cflags harfbuzz) \ + $(pkg-config --cflags harfbuzz-icu) \ + -o OBJECT_FILE""" + + linker_command = config.CC + " " + config.LDFLAGS.get("libsvgbird", "") + """ \ + -shared \ + """ + soname(target_binary) + """ \ + build/libsvgbird/*.o \ + $(pkg-config --libs """ + config.GEE + """) \ + $(pkg-config --libs gio-2.0) \ + $(pkg-config --libs cairo) \ + $(pkg-config --libs glib-2.0) \ + $(pkg-config --libs xmlbird) \ + $(pkg-config --libs fontconfig) \ + $(pkg-config --libs harfbuzz) \ + $(pkg-config --libs harfbuzz-icu) \ + -L./build -L./build/bin \ + -o ./build/bin/""" + target_binary + + link_name = 'libsvgbird.so' + source_directory = 'libsvgbird' + libsvgbird = Builder(source_directory, + valac_command, + cc_command, + linker_command, + target_binary, + link_name, + deps) + + f = open('./build/svgbird.pc', 'w+') + f.write("""prefix=""" + config.PREFIX + """ + exec_prefix=${prefix} + includedir=${prefix}/include + libdir=${exec_prefix}/lib + + Name: svgbird + Description: SVG library + Version: """ + version.LIBSVGBIRD_VERSION + """ + Cflags: -I${includedir} + Libs: -L${libdir} -lsvgbird + Requires: cairo, gobject-2.0, glib-2.0 + """) + + yield libsvgbird.build() + + + def task_libsvgbird(): + yield make_libsvgbird('libsvgbird.so.' + LIBSVGBIRD_SO_VERSION, []) + + def task_svgbird_vapi(): + yield { + 'name': 'merge svgbird vapi', + 'file_dep': ['build/libsvgbird/Object.c'], + 'actions': ['cat svgbirdpoint.vapi > svgbird.vapi && cat svgbird_generated.vapi >> svgbird.vapi'], + 'targets': ['svgbird vapi'] + } def make_libbirdgems(target_binary, deps): valac_command = config.VALAC + """\ -C \ @@ -440,13 +553,15 @@ --pkg gio-2.0 \ --pkg cairo \ --pkg xmlbird \ - --pkg libbirdfont + --pkg libbirdfont \ + --pkg svgbird \ """ cc_command = config.CC + " " + config.CFLAGS.get("birdfont-test", "") + """ \ -c C_SOURCE \ -D 'GETTEXT_PACKAGE="birdfont"' \ -I./build/libbirdfont \ + -I./build/libsvgbird \ $(pkg-config --cflags sqlite3) \ $(pkg-config --cflags """ + config.GEE + """) \ $(pkg-config --cflags gio-2.0) \ @@ -464,7 +579,7 @@ $(pkg-config --libs cairo) \ $(pkg-config --libs glib-2.0) \ $(pkg-config --libs xmlbird) \ - -L./build -L./build/bin -l birdgems\ + -L./build -L./build/bin -l birdgems -l svgbird \ -o build/bin/""" + target_binary test = Builder('birdfont-test', @@ -479,6 +594,5 @@ def task_birdfont_test(): yield make_birdfont_test('birdfont-test', ['libbirdgems.so', 'libbirdfont.so']) -
--- a/install.py +++ b/install.py @@ -48,7 +48,6 @@ print ("install: " + f) run ('install -d ' + dest + prefix + dir) run ('install -m ' + str(mode) + ' ' + file + ' ' + dest + prefix + dir + '/') - installed.write (f + "\n") def install_root (file, dir, mode): f = getDestRoot (file, dir) @@ -60,7 +59,6 @@ f = getDest (linkname, dir) print ("install link: " + f) run ('cd ' + dest + prefix + dir + ' && ln -sf ' + file + ' ' + linkname) - installed.write (f + "\n") if not os.path.exists ("build/configured"): print ("Project is not configured") @@ -73,6 +71,7 @@ parser.add_option ("-l", "--libdir", dest="libdir", help="path to directory for shared libraries (lib or lib64).") parser.add_option ("-c", "--skip-command-line-tools", dest="nocli", help="don't install command line tools") parser.add_option ("-a", "--apport", dest="apport", help="install apport scripts", default=True) + parser.add_option ('-v', '--development', dest='development', action="store_true", help='install development files', metavar='DEVELOPMENT') (options, args) = parser.parse_args() @@ -91,10 +90,6 @@ prefix = config.PREFIX dest = options.dest - - # create uninstall file - installed = open ('build/installed', 'w') - installed.write ('build/installed\n') # install it: install ('resources/icons.bf', '/share/birdfont', 644) @@ -130,8 +125,7 @@ if platform.dist()[0] == 'Ubuntu' or platform.dist()[0] == 'Debian': process = subprocess.Popen(['dpkg-architecture', '-qDEB_HOST_MULTIARCH'], stdout=subprocess.PIPE) out, err = process.communicate() - out = out.decode('ascii') - libdir = '/lib/' + out.rstrip ('\n') + libdir = '/lib/' + out.decode('UTF-8').rstrip ('\n') else: p = platform.machine() if p == 'i386' or p == 's390' or p == 'ppc' or p == 'armv7hl': @@ -144,7 +138,7 @@ libdir = options.libdir if "openbsd" in sys.platform: - install ('build/bin/libbirdfont.so.' + '${LIBbirdfont_VERSION}', '/lib', 644) + install ('build/bin/libbirdfont.so.' + '${LIBbirdfont_VERSION}', '/lib', 644) elif os.path.isfile ('build/bin/libbirdfont.so.' + version.SO_VERSION): install ('build/bin/libbirdfont.so.' + version.SO_VERSION, libdir, 644) link (libdir, 'libbirdfont.so.' + version.SO_VERSION, ' libbirdfont.so.' + version.SO_VERSION_MAJOR) @@ -179,9 +173,25 @@ print ("Can't find libbirdgems, version: " + version.LIBBIRDGEMS_SO_VERSION) exit (1) - - #manpages + if "openbsd" in sys.platform: + install ('build/bin/libsvgbird.so.' + '${LIBsvgbird_VERSION}', '/lib', 644) + elif os.path.isfile ('build/bin/libsvgbird.so.' + version.LIBSVGBIRD_SO_VERSION): + install ('build/bin/libsvgbird.so.' + version.LIBSVGBIRD_SO_VERSION, libdir, 644) + link (libdir, 'libsvgbird.so.' + version.LIBSVGBIRD_SO_VERSION, ' libsvgbird.so.' + version.LIBSVGBIRD_SO_VERSION_MAJOR) + link (libdir, 'libsvgbird.so.' + version.LIBSVGBIRD_SO_VERSION, ' libsvgbird.so') + elif os.path.isfile ('build/libsvgbird.so.' + version.LIBSVGBIRD_SO_VERSION): + install ('build/libsvgbird.so.' + version.LIBSVGBIRD_SO_VERSION, libdir, 644) + link (libdir, 'libsvgbird.so.' + version.LIBSVGBIRD_SO_VERSION, ' libsvgbird.so.' + version.LIBSVGBIRD_SO_VERSION_MAJOR) + link (libdir, 'libsvgbird.so.' + version.LIBSVGBIRD_SO_VERSION, ' libsvgbird.so') + elif os.path.isfile ('build/bin/libsvgbird.' + version.LIBSVGBIRD_SO_VERSION + '.dylib'): + install ('build/bin/libsvgbird.' + version.LIBSVGBIRD_SO_VERSION + '.dylib', libdir, 644) + link (libdir, 'libsvgbird.' + version.LIBSVGBIRD_SO_VERSION + '.dylib', ' libsvgbird.dylib.' + version.LIBSVGBIRD_SO_VERSION_MAJOR) + link (libdir, 'libsvgbird.' + version.LIBSVGBIRD_SO_VERSION + '.dylib', ' libsvgbird.dylib') + else: + print ("Can't find libsvgbird, version: " + version.LIBSVGBIRD_SO_VERSION) + exit (1) + #manpages if not nogzip: install ('build/birdfont.1.gz', mandir, 644) @@ -211,5 +221,11 @@ install ('resources/source_birdfont.py', '/share/apport/package-hooks', 644) install_root ('resources/birdfont-crashdb.conf', '/etc/apport/crashdb.conf.d', 644) - installed.close () + #install development files + if options.development: + install ('build/libsvgbird/svgbird.h', '/include', 644) + install ('build/libsvgbird/point_value.h', '/include', 644) + install ('svgbird.vapi', '/share/vala/vapi', 644) + install ('svgbird.deps', '/share/vala/vapi', 644) + install ('build/svgbird.pc', libdir + '/pkgconfig', 644)
--- a/libbirdfont/BackgroundTab.vala +++ b/libbirdfont/BackgroundTab.vala @@ -14,6 +14,7 @@ using Math; using Cairo; + using SvgBird; namespace BirdFont {
--- a/libbirdfont/BackgroundTools.vala +++ b/libbirdfont/BackgroundTools.vala @@ -112,6 +112,7 @@ add_part (selection); } + parts.clear_cache (); parts.redraw (); }
diff --git libbirdfont/BezierPoints.vala(deleted)
--- a/libbirdfont/BezierPoints.vala +++ /dev/null @@ -1,34 +1,1 @@ - /* - Copyright (C) 2014 Johan Mattsson - - 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 3 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. - */ - - namespace BirdFont { - - /** Bezier point container for the SVG parser. */ - public class BezierPoints { - public unichar type = '\0'; - public unichar svg_type = '\0'; - public double x0 = 0; - public double y0 = 0; - public double x1 = 0; - public double y1 = 0; - public double x2 = 0; - public double y2 = 0; - - public string to_string () { - return @"$((!)type.to_string ()) $x0,$y0 $x1,$y1 $x2,$y2 SVG:$((!)svg_type.to_string ())"; - } - } - - }
--- a/libbirdfont/BezierTool.vala +++ b/libbirdfont/BezierTool.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2015 Johan Mattsson + Copyright (C) 2015 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -175,7 +175,7 @@ current_path.recalculate_linear_handles_for_point (current_point); set_point_type (); g.clear_active_paths (); - g.add_active_path (null, current_path); + g.add_active_path (current_path); GlyphCanvas.redraw (); state = MOVE_POINT; } else { @@ -282,7 +282,7 @@ current_path.recalculate_linear_handles_for_point (current_point); set_point_type (); g.clear_active_paths (); - g.add_active_path (null, current_path); + g.add_active_path (current_path); GlyphCanvas.redraw (); state = MOVE_POINT;
--- a/libbirdfont/BirdFont.vala +++ b/libbirdfont/BirdFont.vala @@ -90,7 +90,6 @@ public static Font current_font; public static GlyphCollection current_glyph_collection; - public static Drawing? drawing = null; public static string? settings_subdirectory = null; @@ -166,6 +165,8 @@ Process.exit (0); } #endif + + CanvasSettings.init (); Preferences.load (); // always load default theme when names in theme does change @@ -525,33 +526,33 @@ /** Obtain a handle to a file in a folder. */ public static File get_child (File folder, string file_name) { - string f; - string s; - string n; + string folder_path; + string separator; + string name; // avoid drive letter problems on windows - f = (!) folder.get_path (); + folder_path = (!) folder.get_path (); #if LINUX - s = "/"; + separator = "/"; #else - s = (BirdFont.win32) ? "\\" : "/"; + separator = (BirdFont.win32) ? "\\" : "/"; #endif - n = file_name; + name = file_name; if (unlikely (BirdFont.win32 && file_name.index_of ("\\") != -1)) { - warning (@"File name contains path separator: $file_name, Directory: $f"); - n = n.substring (n.last_index_of ("\\")).replace ("\\", ""); + warning (@"File name contains path separator: $file_name, Directory: $folder_path"); + name = name.substring (name.last_index_of ("\\")).replace ("\\", ""); } - if (!f.has_suffix (s)) { - f += s; + if (!folder_path.has_suffix (separator)) { + folder_path += separator; } - printd (@"File in Directory: $f Name: $n\n"); + printd (@"File in Directory: $folder_path Name: $name\n"); - return File.new_for_path (f + n); + return File.new_for_path (folder_path + name); } public static void set_drawing_callbacks (Drawing callbacks) {
--- a/libbirdfont/BirdFontFile.vala +++ b/libbirdfont/BirdFontFile.vala @@ -11,7 +11,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ + using B; + using SvgBird; namespace BirdFont { @@ -24,7 +26,7 @@ Font font; public static const int FORMAT_MAJOR = 2; - public static const int FORMAT_MINOR = 2; + public static const int FORMAT_MINOR = 3; public static const int MIN_FORMAT_MAJOR = 0; public static const int MIN_FORMAT_MINOR = 0; @@ -446,7 +448,7 @@ public void write_glyph (Glyph g, DataOutputStream os) throws GLib.Error { os.put_string (@"\t<glyph id=\"$(g.version_id)\" left=\"$(double_to_string (g.left_limit))\" right=\"$(double_to_string (g.right_limit))\">\n"); - foreach (Layer layer in g.layers.subgroups) { + foreach (Layer layer in g.layers.get_sublayers ()) { write_layer (layer, os); } @@ -454,38 +456,111 @@ os.put_string ("\t</glyph>\n"); } - void write_layer (Layer layer, DataOutputStream os) throws GLib.Error { - string data; + void write_embedded_svg (EmbeddedSvg svg, DataOutputStream os) throws GLib.Error { + XmlParser xml = new XmlParser ((!) svg.svg_data); - // FIXME: name etc. + if (xml.validate ()) { + os.put_string (@"<embedded "); + os.put_string (@"type=\"svg\" "); + os.put_string (@"x=\"$(round (svg.x))\" "); + os.put_string (@"y=\"$(round (svg.y))\" "); + os.put_string (@"transform=\"$(svg.drawing.transforms.get_xml ())\""); + os.put_string (@">\n"); + + Tag tag = xml.get_root_tag (); + + os.put_string ("<"); + os.put_string (tag.get_name ()); + + os.put_string (" "); + write_tag_attributes (os, tag); + + string content = tag.get_content (); + + if (content == "") { + os.put_string (" /"); + } + + os.put_string (">"); + + os.put_string (content); + + os.put_string ("</"); + os.put_string (tag.get_name ()); + os.put_string (">\n"); + + os.put_string ("</embedded>\n"); + } + } + + void write_tag_attributes (DataOutputStream os, Tag tag) throws GLib.Error { + bool first = true; + + foreach (Attribute attribute in tag.get_attributes ()) { + string ns = attribute.get_namespace (); + + if (!first) { + os.put_string (" "); + } + + if (ns != "") { + os.put_string (ns); + os.put_string (":"); + } + + os.put_string (attribute.get_name ()); + os.put_string ("="); + os.put_string ("\""); + os.put_string (attribute.get_content ()); + os.put_string ("\""); + + first = false; + } + } + + void write_layer (Layer layer, DataOutputStream os) throws GLib.Error { os.put_string (@"\t\t<layer name= \"$(layer.name)\" visible=\"$(layer.visible)\">\n"); - foreach (Path p in layer.get_all_paths ().paths) { - data = get_point_data (p); - if (data != "") { - os.put_string (@"\t\t\t<path "); - - if (p.stroke != 0) { - os.put_string (@"stroke=\"$(double_to_string (p.stroke))\" "); - } - - if (p.line_cap != LineCap.BUTT) { - if (p.line_cap == LineCap.ROUND) { - os.put_string (@"cap=\"round\" "); - } else if (p.line_cap == LineCap.SQUARE) { - os.put_string (@"cap=\"square\" "); - } - } - - if (p.skew != 0) { - os.put_string (@"skew=\"$(double_to_string (p.skew))\" "); - } - - os.put_string (@"data=\"$(data)\" />\n"); + foreach (SvgBird.Object o in layer.objects.objects) { + + if (o is EmbeddedSvg) { + write_embedded_svg ((EmbeddedSvg) o, os); } + + if (o is PathObject) { + Path p = ((PathObject) o).get_path (); + write_path_object (p, os); + } } os.put_string ("\t\t</layer>\n"); + } + + void write_path_object (Path p, DataOutputStream os) throws GLib.Error { + string data; + + data = get_point_data (p); + if (data != "") { + os.put_string (@"\t\t\t<path "); + + if (p.stroke != 0) { + os.put_string (@"stroke=\"$(double_to_string (p.stroke))\" "); + } + + if (p.line_cap != LineCap.BUTT) { + if (p.line_cap == LineCap.ROUND) { + os.put_string (@"cap=\"round\" "); + } else if (p.line_cap == LineCap.SQUARE) { + os.put_string (@"cap=\"square\" "); + } + } + + if (p.skew != 0) { + os.put_string (@"skew=\"$(double_to_string (p.skew))\" "); + } + + os.put_string (@"data=\"$(data)\" />\n"); + } } public static string double_to_string (double n) { @@ -908,7 +983,7 @@ } font.format_major = int.parse (v[0]); - font.format_major = int.parse (v[1]); + font.format_minor = int.parse (v[1]); } public void parse_images (Tag tag) { @@ -1407,7 +1482,7 @@ foreach (Tag t in tag) { if (t.get_name () == "background") { parse_background_scale (glyph, t); - } + } } foreach (Path p in glyph.get_all_paths ()) { @@ -1418,6 +1493,39 @@ gc.set_unassigned (unassigned); master.insert_glyph (glyph, selected || selected_id == id); + } + + void parse_embedded_svg (Layer layer, Tag tag) { + string type = ""; + double x = 0; + double y = 0; + string transform = ""; + + foreach (Attribute attribute in tag.get_attributes ()) { + if (attribute.get_name () == "x") { + x = parse_double (attribute.get_content ()); + } + + if (attribute.get_name () == "y") { + y = parse_double (attribute.get_content ()); + } + + if (attribute.get_name () == "type") { + type = attribute.get_content (); + } + + if (attribute.get_name () == "transform") { + transform = attribute.get_content (); + } + } + + if (type == "svg") { + EmbeddedSvg svg = SvgParser.parse_embedded_svg_data (tag.get_content ()); + svg.drawing.transforms = SvgFile.parse_transform (transform); + svg.x = x; + svg.y = y; + layer.add_object (svg); + } } Layer parse_layer (Tag tag) { @@ -1437,7 +1545,11 @@ foreach (Tag t in tag) { if (t.get_name () == "path") { path = parse_path (t); - layer.add_path (path); + LayerUtils.add_path (layer, path); + } + + if (t.get_name () == "embedded") { + parse_embedded_svg (layer, t); } } @@ -1472,7 +1584,7 @@ } } - return path; + return path; } private static void line (Path path, string px, string py) { @@ -1878,7 +1990,8 @@ ligatures = font.get_ligatures (); ligatures.add_ligature (sequence, ligature); } + } }
--- /dev/null +++ b/libbirdfont/CanvasSettings.vala @@ -1,1 +1,93 @@ + /* + Copyright (C) 2016 Johan Mattsson + + 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 3 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. + */ + + using B; + using Cairo; + + namespace BirdFont { + + public class CanvasSettings { + + public static double stroke_width { + get { + settings_mutex.lock (); + double r = stroke_width_setting; + + if (unlikely (stroke_width_setting < 1)) { + string width = Preferences.get ("stroke_width"); + if (width != "") { + stroke_width_setting = double.parse (width); + } + } + + if (stroke_width_setting < 1) { + stroke_width_setting = 1; + } + + settings_mutex.unlock (); + return r; + } + + set { + settings_mutex.lock (); + stroke_width_setting = value; + settings_mutex.unlock (); + } + } + + public static bool show_all_line_handles { + get { + settings_mutex.lock (); + bool r = show_all_line_handles_setting; + settings_mutex.unlock (); + return r; + } + + set { + settings_mutex.lock (); + show_all_line_handles_setting = value; + settings_mutex.unlock (); + } + } + + public static bool fill_open_path { + get { + settings_mutex.lock (); + bool r = fill_open_path_setting; + settings_mutex.unlock (); + return r; + } + + set { + settings_mutex.lock (); + fill_open_path_setting = value; + settings_mutex.unlock (); + } + } + + static Mutex settings_mutex; + + /** The stroke of an outline when the path is not filled. */ + static double stroke_width_setting = 0; + static bool show_all_line_handles_setting = true; + static bool fill_open_path_setting = false; + + public static void init () { + settings_mutex = new Mutex (); + } + + } + + }
--- a/libbirdfont/CircleTool.vala +++ b/libbirdfont/CircleTool.vala @@ -184,8 +184,8 @@ glyph.store_undo_state (); - double px = Glyph.path_coordinate_x (x); - double py = Glyph.path_coordinate_y (y); + double px = Glyph.path_coordinate_x (x); + double py = Glyph.path_coordinate_y (y); path = create_circle (px, py, 2, DrawingTools.point_type);
--- a/libbirdfont/ClipTool.vala +++ b/libbirdfont/ClipTool.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2014 Johan Mattsson + Copyright (C) 2012 2014 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -11,6 +11,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ + + using SvgBird; namespace BirdFont { @@ -95,7 +97,7 @@ dx = g.motion_x - x - w / 2.0; dy = g.motion_y - y + h / 2.0; - foreach (Path path in g.active_paths) { + foreach (SvgBird.Object path in g.active_paths) { path.move (dx, dy); } } else if (fd is KerningDisplay) { @@ -144,7 +146,7 @@ if (bf_clipboard_data) { import_birdfont_clipboard (data, paste_guide_lines, false); } else if (data != "") { - SvgParser.import_svg_data (data, SvgFormat.INKSCAPE); + SvgParser.import_svg_color_data (data); } ((!)destination).update_view (); @@ -261,7 +263,7 @@ } } } else if (glyph.get_visible_paths ().size > 0) { - foreach (Path path in glyph.active_paths) { + foreach (Path path in glyph.get_active_paths ()) { // FIXME: other objects s.append ("BF path: "); s.append (BirdFontFile.get_point_data (path)); s.append ("\n"); @@ -406,9 +408,9 @@ string cap = p.replace ("cap: ", ""); if (cap == "round") { - path.line_cap = LineCap.ROUND; + path.line_cap = SvgBird.LineCap.ROUND; } else if (cap == "square") { - path.line_cap = LineCap.SQUARE; + path.line_cap = SvgBird.LineCap.SQUARE; } } } @@ -421,7 +423,7 @@ foreach (Path p in glyph.get_visible_paths ()) { PenTool.clear_directions (); destination.add_path (p); - destination.add_active_path (null, p); + destination.add_active_path (p); } if (paste_guide_lines) { @@ -448,13 +450,13 @@ if (path.points.size > 0) { PenTool.clear_directions (); glyph.add_path (path); - glyph.active_paths.add (path); + glyph.add_active_path (path); path.update_region_boundaries (); } PenTool.remove_all_selected_points (); - foreach (Path p in glyph.active_paths) { + foreach (Path p in glyph.get_active_paths ()) { if (p.is_open ()) { foreach (EditPoint e in p.points) { e.set_selected (true);
--- a/libbirdfont/Color.vala +++ b/libbirdfont/Color.vala @@ -1,168 +1,30 @@ - /* Copyright (C) 1999 The Free Software Foundation - * - * Authors: Simon Budig <Simon.Budig@unix-ag.org> (original code) - * Federico Mena-Quintero <federico@gimp.org> (cleanup for GTK+) - * Jonathan Blandford <jrb@redhat.com> (cleanup for GTK+) - * - * 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., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ + /* + Copyright (C) 2016 Johan Mattsson + + 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 3 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. + */ namespace BirdFont { - public class Color { - public double r; - public double g; - public double b; - public double a; - + public class Color : SvgBird.Color { public Color (double r, double g, double b, double a) { - this.r = r; - this.g = g; - this.b = b; - this.a = a; + base (r, g, b, a); } + public Color.create_copy (SvgBird.Color color) { + base (color.r, color.g, color.b, color.a); + } + public Color.hsba (double h, double s, double v, double a) { - double hue, saturation, value; - double f, p, q, t; - - this.a = a; - - if (s == 0.0) { - r = v; - g = v; - b = v; - } else { - hue = h * 6.0; - saturation = s; - value = v; - - if (hue == 6.0) { - hue = 0.0; - } - - f = hue - (int) hue; - p = value * (1.0 - saturation); - q = value * (1.0 - saturation * f); - t = value * (1.0 - saturation * (1.0 - f)); - - switch ((int) hue) { - case 0: - r = value; - g = t; - b = p; - break; - - case 1: - r = q; - g = value; - b = p; - break; - - case 2: - r = p; - g = value; - b = t; - break; - - case 3: - r = p; - g = q; - b = value; - break; - - case 4: - r = t; - g = p; - b = value; - break; - - case 5: - r = value; - g = p; - b = q; - break; - - default: - assert_not_reached (); - } - } - } - - public void to_hsva (out double h, out double s, out double v, out double a) { - double red, green, blue; - double min, max; - double delta; - - a = this.a; - - red = r; - green = g; - blue = b; - - h = 0.0; - - if (red > green) { - if (red > blue) - max = red; - else - max = blue; - - if (green < blue) - min = green; - else - min = blue; - } else { - if (green > blue) - max = green; - else - max = blue; - - if (red < blue) - min = red; - else - min = blue; - } - - v = max; - - if (max != 0.0) - s = (max - min) / max; - else - s = 0.0; - - if (s == 0.0) - h = 0.0; - else { - delta = max - min; - - if (red == max) - h = (green - blue) / delta; - else if (green == max) - h = 2 + (blue - red) / delta; - else if (blue == max) - h = 4 + (red - green) / delta; - - h /= 6.0; - - if (h < 0.0) - h += 1.0; - else if (h > 1.0) - h -= 1.0; - } + base.hsva (h, s, v, a); } public static Color black () { @@ -204,23 +66,11 @@ public static Color magenta () { return new Color (103.0 / 255, 33.0 / 255, 120.0 / 255, 1); } - - public string to_string () { - return @"r: $r, g: $g, b: $b, a: $a"; - } - - public Color copy () { + + public new Color copy () { return new Color (r, g, b, a); - } - - public string to_rgb_hex () { - string s = "#"; - s += Font.to_hex_code ((unichar) Math.rint (r * 254)); - s += Font.to_hex_code ((unichar) Math.rint (g * 254)); - s += Font.to_hex_code ((unichar) Math.rint (b * 254)); - return s; } } }
--- a/libbirdfont/ColorPicker.vala +++ b/libbirdfont/ColorPicker.vala @@ -13,6 +13,7 @@ */ using Cairo; + using SvgBird; namespace BirdFont { @@ -38,7 +39,7 @@ public Color stroke_color = new Color (0, 0, 0, 1); public Color fill_color = new Color (0, 0, 0, 1); - public Gradient gradient = new Gradient (); + public LinearGradient gradient = new LinearGradient (); bool update_gradient = false; int bars; Stop current_stop = new Stop (); @@ -76,7 +77,7 @@ }); } - public void set_gradient (Gradient g, Stop stop, bool update_gradient) { + public void set_gradient (LinearGradient g, Stop stop, bool update_gradient) { gradient = g; this.update_gradient = update_gradient; current_stop = stop; @@ -119,7 +120,7 @@ int g = (int) ((tx / Toolbox.allocation_width) * gradient.stops.size); return_if_fail (0 <= g < gradient.stops.size); current_stop = gradient.stops.get (g); - set_color (current_stop.color); + set_color (new Color.create_copy (current_stop.color)); } } @@ -146,7 +147,7 @@ public Color get_fill_color () { return fill_color; } - + public override void draw_tool (Context cr, double px, double py) { draw_bars (cr, px, py); draw_dial (cr, px, py, 0, hue); @@ -236,7 +237,7 @@ int stop_size = (int) ((double) Toolbox.allocation_width / gradient.stops.size); for (int i = 0; i < gradient.stops.size; i++) { Stop s = gradient.stops.get (i); - c = s.color; + c = new Color.create_copy (s.color); cr.save (); cr.set_source_rgba (c.r, c.g, c.b, c.a); cr.rectangle (i * stop_size, y + 4 * bar_height, stop_size, bar_height);
--- a/libbirdfont/DrawingTools.vala +++ b/libbirdfont/DrawingTools.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2013 2014 2015 Johan Mattsson + Copyright (C) 2012 2013 2014 2015 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -14,6 +14,7 @@ using Cairo; using Math; + using SvgBird; namespace BirdFont { @@ -101,7 +102,7 @@ Tool move_layer; Tool flip_vertical; Tool flip_horizontal; - Tool full_height_tool; + Tool monochrome_tool; public ZoomBar zoom_bar; @@ -194,6 +195,7 @@ move_canvas = new Tool ("move_canvas", t_("Move canvas") + "\n" + t_("Ctrl + Shift + Click") + "\n" + t_("Space + Click") + "\n"); + move_canvas.select_action.connect ((self) => { update_drawing_and_background_tools (self); }); @@ -240,7 +242,10 @@ key_tools.add_tool (insert_point_on_path_tool); // quadratic Bézier points - quadratic_points = new Tool ("quadratic_points", t_("Create quadratic Bézier curves")); + quadratic_points = new Tool ("quadratic_points", + t_("Create quadratic Bézier curves"), + t_("All control points will be converted to quadratic points in the TTF format.")); + quadratic_points.select_action.connect ((self) => { point_type = PointType.QUADRATIC; Preferences.set ("point_type", "quadratic_points"); @@ -294,7 +299,7 @@ glyph.selection_boundaries (out x, out y, out w, out h); delta = x_coordinate.get_value () - x + glyph.left_limit; - foreach (Path path in glyph.active_paths) { + foreach (SvgBird.Object path in glyph.active_paths) { path.move (delta, 0); } @@ -337,7 +342,7 @@ glyph.selection_boundaries (out x, out y, out w, out h); - foreach (Path path in glyph.active_paths) { + foreach (Path path in glyph.get_active_paths ()) { path.move (0, y_coordinate.get_value () - (y - h) - font.base_line); } @@ -377,7 +382,7 @@ double x, y, w, h; Glyph glyph = MainWindow.get_current_glyph (); double angle = (self.get_value () / 360) * 2 * PI; - Path last_path; + SvgBird.Object last_path; glyph.selection_boundaries (out x, out y, out w, out h); x += w / 2; @@ -385,10 +390,40 @@ if (glyph.active_paths.size > 0) { last_path = glyph.active_paths.get (glyph.active_paths.size - 1); - resize_tool.rotate_selected_paths (angle - last_path.rotation, x, y); + + double last_angle; + + if (last_path is PathObject) { + last_angle = ((PathObject) last_path).get_path ().rotation; + } else { + last_angle = last_path.transforms.total_rotation; + } + + double r = angle - last_angle; + resize_tool.rotate_selected_paths (r, x, y); } GlyphCanvas.redraw (); + }); + + move_tool.selection_changed.connect (() => { + Glyph glyph = MainWindow.get_current_glyph (); + SvgBird.Object path; + + if (glyph.active_paths.size > 0) { + path = glyph.active_paths.get (glyph.active_paths.size - 1); + + double angle; + + if (path is PathObject) { + angle = ((PathObject) path).get_path ().rotation; + } else { + angle = path.transforms.total_rotation; + } + + angle *= 180 / PI; + rotation.set_value_round (angle, true, false); + } }); resize_tool.objects_rotated.connect ((angle) => { @@ -427,7 +462,7 @@ GlyphCanvas.redraw (); }); draw_tool_modifiers.add_tool (width); - + // height height = new SpinButton ("height", t_("Height")); height.set_big_number (true); @@ -482,7 +517,7 @@ tie = !p.tie_handles; // don't tie end points - foreach (Path path in MainWindow.get_current_glyph ().active_paths) { + foreach (Path path in MainWindow.get_current_glyph ().get_active_paths ()) { if (path.is_open ()) { if (p == path.get_first_point () || p == path.get_last_point ()) { tie = false; @@ -564,13 +599,13 @@ reverse_path_tool = new OrientationTool ("reverse_path", t_("Create counter from outline")); draw_tool_modifiers.add_tool (reverse_path_tool); - full_height_tool = new Tool ("full_height", t_("Scale object to font top/baseline")); - full_height_tool.select_action.connect ((self) => { - resize_tool.full_height (); - GlyphCanvas.redraw (); + monochrome_tool = new Tool ("svg_to_birdfont", t_("Convert SVG file to monochrome glyph")); + monochrome_tool.select_action.connect ((self) => { + MainWindow.get_current_glyph ().store_undo_state (); + move_tool.convert_svg_to_monochrome (); }); - draw_tool_modifiers.add_tool (full_height_tool); + draw_tool_modifiers.add_tool (monochrome_tool); // close path close_path_tool = new Tool ("close_path", t_("Close path")); @@ -600,9 +635,9 @@ Glyph g = MainWindow.get_current_glyph (); Layer layer = g.get_current_layer (); - foreach (Path p in g.active_paths) { - layer.paths.remove (p); - layer.paths.paths.insert (0, p); + foreach (SvgBird.Object p in g.active_paths) { + layer.remove (p); + layer.objects.objects.insert (0, p); } GlyphCanvas.redraw (); @@ -800,13 +835,19 @@ g.store_undo_state (); if (StrokeTool.add_stroke) { - foreach (Path p in g.active_paths) { - p.stroke = StrokeTool.stroke_width; - p.line_cap = StrokeTool.line_cap; + foreach (SvgBird.Object p in g.active_paths) { + if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + path.stroke = StrokeTool.stroke_width; + path.line_cap = StrokeTool.line_cap; + } } } else { - foreach (Path p in g.active_paths) { - p.stroke = 0; + foreach (SvgBird.Object p in g.active_paths) { + if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + path.stroke = 0; + } } } @@ -840,9 +881,12 @@ StrokeTool.stroke_width = object_stroke.get_value (); if (tool && StrokeTool.add_stroke) { - foreach (Path p in g.active_paths) { - p.stroke = StrokeTool.stroke_width; - p.reset_stroke (); + foreach (SvgBird.Object p in g.active_paths) { + if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + path.stroke = StrokeTool.stroke_width; + path.reset_stroke (); + } } } @@ -878,12 +922,15 @@ g = MainWindow.get_current_glyph (); g.store_undo_state (); - foreach (Path p in g.active_paths) { - p.line_cap = LineCap.BUTT; - p.reset_stroke (); + foreach (SvgBird.Object p in g.active_paths) { + p.style.line_cap = SvgBird.LineCap.BUTT; + + if (p is PathObject) { + ((PathObject) p).get_path ().reset_stroke (); + } } - StrokeTool.line_cap = LineCap.BUTT; + StrokeTool.line_cap = SvgBird.LineCap.BUTT; Font f = BirdFont.get_current_font (); f.settings.set_setting ("line_cap", @"butt"); @@ -902,12 +949,15 @@ g = MainWindow.get_current_glyph (); g.store_undo_state (); - foreach (Path p in g.active_paths) { - p.line_cap = LineCap.ROUND; - p.reset_stroke (); + foreach (SvgBird.Object p in g.active_paths) { + if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + path.line_cap = SvgBird.LineCap.ROUND; + path.reset_stroke (); + } } - StrokeTool.line_cap = LineCap.ROUND; + StrokeTool.line_cap = SvgBird.LineCap.ROUND; Font f = BirdFont.get_current_font (); f.settings.set_setting ("line_cap", @"round"); @@ -918,6 +968,7 @@ GlyphCanvas.redraw (); }); + stroke_expander.add_tool (line_cap_round); line_cap_square = new Tool ("line_cap_square", t_("Square line cap")); @@ -927,12 +978,15 @@ g = MainWindow.get_current_glyph (); g.store_undo_state (); - foreach (Path p in g.active_paths) { - p.line_cap = LineCap.SQUARE; - p.reset_stroke (); + foreach (SvgBird.Object p in g.active_paths) { + if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + path.line_cap = SvgBird.LineCap.SQUARE; + path.reset_stroke (); + } } - StrokeTool.line_cap = LineCap.SQUARE; + StrokeTool.line_cap = SvgBird.LineCap.SQUARE; Font f = BirdFont.get_current_font (); f.settings.set_setting ("line_cap", @"square"); @@ -1259,8 +1313,8 @@ bool stroke = false; Glyph g = MainWindow.get_current_glyph (); - foreach (Path p in g.active_paths) { - if (p.stroke > 0) { + foreach (SvgBird.Object p in g.active_paths) { + if (p.style.stroke_width > 0) { stroke = true; } } @@ -1310,7 +1364,7 @@ height.set_tool_visibility (false); reverse_path_tool.set_tool_visibility (false); - full_height_tool.set_tool_visibility (false); + monochrome_tool.set_tool_visibility (false); move_layer.set_tool_visibility (false); flip_vertical.set_tool_visibility (false); flip_horizontal.set_tool_visibility (false); @@ -1382,7 +1436,7 @@ flip_vertical.set_tool_visibility (true); flip_horizontal.set_tool_visibility (true); - full_height_tool.set_tool_visibility (true); + monochrome_tool.set_tool_visibility (true); } public override void reset_selection (Tool current_tool) { @@ -1617,7 +1671,7 @@ int i = 0; layer_tools.tool.clear (); - foreach (Layer layer in g.layers.subgroups) { + foreach (Layer layer in g.layers.get_sublayers ()) { LayerLabel label = new LayerLabel (layer); layer_tools.add_tool (label, 0);
--- /dev/null +++ b/libbirdfont/EmbeddedSvg.vala @@ -1,1 +1,134 @@ + /* + Copyright (C) 2016 Johan Mattsson + + 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 3 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. + */ + + using B; + using Math; + using Cairo; + using SvgBird; + + namespace BirdFont { + + public class EmbeddedSvg : SvgBird.Object { + public string svg_data = ""; + + // the view matrix belongs to the drawing + public SvgDrawing drawing = new SvgDrawing (); + + public double x; + public double y; + + public EmbeddedSvg (SvgDrawing drawing) { + this.drawing = drawing; + } + + public override bool update_boundaries (Context context) { + drawing.update_boundaries (context); + + left = x + drawing.left; + right = x + drawing.right; + top = -y + drawing.top; + bottom = -y + drawing.bottom; + + return true; + } + + public override bool is_over (double x, double y) { + return (xmin <= x <= xmax) + && (ymin <= y <= ymax); + } + + public void draw_embedded_svg (Context cr) { + cr.save (); + cr.translate (Glyph.xc () + x, Glyph.yc () - y); + apply_transform (cr); + drawing.draw (cr); + cr.restore (); + } + + public override void draw_outline (Context cr) { + drawing.draw_outline (cr); + } + + public override void move (double dx, double dy) { + x += dx; + y += dy; + move_bounding_box (dx, -dy); + } + + public override bool is_empty () { + return drawing.is_empty (); + } + + public override SvgBird.Object copy () { + EmbeddedSvg svg = new EmbeddedSvg ((SvgDrawing) drawing.copy ()); + SvgBird.Object.copy_attributes (this, svg); + svg.svg_data = svg_data; + svg.x = x; + svg.y = y; + return svg; + } + + public override string to_string () { + return "Embedded SVG"; + } + + public string get_transformed_svg_data () { + StringBuilder svg = new StringBuilder (); + + svg.append ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); + svg.append ("""<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">"""); + svg.append ("\n"); + + string transforms = drawing.transforms.get_xml (); + + if (transforms != "") { + svg.append ("<g"); + svg.append (@" transform=\"$(transforms)\""); + svg.append (">\n"); + } + + svg.append (remove_xml_header (svg_data)); + + if (transforms != "") { + svg.append ("</g>\n"); + } + + svg.append ("</svg>\n"); + + return svg.str; + } + + public string remove_xml_header (string xml_data) { + string xml = xml_data; + + int start = xml.index_of ("<?"); + while (start > -1) { + int end = xml.index_of ("?>"); + + if (end == -1) { + return xml; + } + + end += "?>".length; + + xml = xml.substring (0, start) + xml.substring (end); + start = xml.index_of ("<?"); + } + + return xml; + } + } + + }
--- a/libbirdfont/Expander.vala +++ b/libbirdfont/Expander.vala @@ -61,6 +61,10 @@ public void clear_cache () { cached = null; + + foreach (Tool t in tool) { + t.clear_cache (); + } } public void set_headline (Text h) {
--- a/libbirdfont/ExportTool.vala +++ b/libbirdfont/ExportTool.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2014 2015 Johan Mattsson + Copyright (C) 2012 - 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -13,6 +13,7 @@ */ using B; + using SvgBird; namespace BirdFont { @@ -59,8 +60,10 @@ public static string export_to_string (Glyph glyph, bool only_selected_paths) { string name; - StringBuilder s; - + StringBuilder s; + Font font; + + font = BirdFont.get_current_font (); name = XmlParser.encode (glyph.get_name ()); s = new StringBuilder (); @@ -73,7 +76,7 @@ x="0px" y="0px" width=""" + "\"" + @"$(glyph.get_width ())" + """px" - height=""" + "\"" + @"$(glyph.get_height ())" + """px"> + height=""" + "\"" + @"$(font.top_position - font.bottom_position)" + """px"> """); s.append (@"<g id=\"$(name)\">\n"); @@ -119,37 +122,50 @@ name = glyph.get_name (); - Gee.ArrayList<Path> pl; + Gee.ArrayList<SvgBird.Object> pl; s = new StringBuilder (); glyph_svg = ""; - pl = only_selected_paths ? glyph.active_paths : glyph.get_visible_paths (); - foreach (Path p in pl) { - if (p.stroke > 0) { - s.append (@"<path "); - s.append (@"style=\""); - s.append (@"fill:none;"); - s.append (@"stroke:#000000;"); - s.append (@"stroke-width:$(p.stroke)px;"); - - if (p.line_cap == LineCap.ROUND) { - s.append (@"stroke-linecap:round;"); - } else if (p.line_cap == LineCap.SQUARE) { - s.append (@"stroke-linecap:square;"); + pl = only_selected_paths ? glyph.active_paths : glyph.get_visible_objects (); + + foreach (SvgBird.Object o in pl) { + + if (o is PathObject) { + Path p = ((PathObject) o).get_path (); + + if (p.stroke > 0) { + s.append (@"<path "); + s.append (@"style=\""); + s.append (@"fill:none;"); + s.append (@"stroke:#000000;"); + s.append (@"stroke-width:$(p.stroke)px;"); + + if (p.line_cap == LineCap.ROUND) { + s.append (@"stroke-linecap:round;"); + } else if (p.line_cap == LineCap.SQUARE) { + s.append (@"stroke-linecap:square;"); + } + + s.append (@"\" "); + + s.append (@"d=\"$(Svg.to_svg_path (p, glyph))\" id=\"path_$(name)_$(id)\" />\n"); + id++; } - - s.append (@"\" "); - - s.append (@"d=\"$(Svg.to_svg_path (p, glyph))\" id=\"path_$(name)_$(id)\" />\n"); - id++; + } else { + warning ("Copy and paste for other objects not implemented."); } } if (only_selected_paths) { - foreach (Path p in glyph.active_paths) { - if (p.stroke == 0) { - glyph_svg += Svg.to_svg_path (p, glyph); + foreach (SvgBird.Object p in glyph.active_paths) { + if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + if (path.stroke == 0) { + glyph_svg += Svg.to_svg_path (path, glyph); + } + } else { + warning ("Not implemented"); } } } else {
--- a/libbirdfont/FileDialogTab.vala +++ b/libbirdfont/FileDialogTab.vala @@ -203,11 +203,14 @@ } files.sort (); + update_rows (); layout (); base.selected_canvas (); scroll_to (0); + MainWindow.show_scrollbar (); + update_scrollbar (); } public void show_text_area (string text) { @@ -260,6 +263,8 @@ action.file_selected ((!) f.get_path ()); } } + + MainWindow.show_scrollbar (); } public override string get_label () { @@ -273,6 +278,7 @@ public override void button_release (int button, double ex, double ey) { base.button_release (button, ex, ey); show_text_area (selected_filename); + MainWindow.show_scrollbar (); } class SelectedFile : GLib.Object {
--- a/libbirdfont/Font.vala +++ b/libbirdfont/Font.vala @@ -196,8 +196,17 @@ } public void add_default_characters () { - add_glyph_collection (get_notdef_character ()); - add_glyph_collection (get_space ()); + GlyphCollection notdef = get_notdef_character (); + + if (!has_glyph (notdef.get_name ())) { + add_glyph_collection (notdef); + } + + GlyphCollection space = get_space (); + + if (!has_glyph (space.get_name ())) { + add_glyph_collection (space); + } } public Alternate? get_alternate (string glyph_name, string tag) { @@ -597,7 +606,8 @@ gc = glyph_name.get (glyph_collection.get_name ()); if (unlikely (gc != null)) { - warning ("glyph has already been added"); + GlyphCollection added_glyph = (!) gc; + warning (@"glyph has already been added $(added_glyph.get_name ())"); return; }
--- a/libbirdfont/Glyph.vala +++ b/libbirdfont/Glyph.vala @@ -15,6 +15,8 @@ using Cairo; using Math; using Gee; + using B; + using SvgBird; namespace BirdFont { @@ -129,22 +131,26 @@ public static bool show_orientation_arrow = false; public static double orientation_arrow_opacity = 1; - public Layer layers = new Layer (); + public Layer layers = new Layer.with_name ("Root"); public int current_layer = 0; - public Gee.ArrayList<Path> active_paths = new Gee.ArrayList<Path> (); - public Gee.ArrayList<Layer> selected_groups = new Gee.ArrayList<Layer> (); + public Gee.ArrayList<SvgBird.Object> active_paths = new Gee.ArrayList<SvgBird.Object> (); - // used if this glyph is fetched from a fallback font + // used if this glyph originates from a fallback font public double top_limit = 0; public double baseline = 0; public double bottom_limit = 0; public Surface? overview_thumbnail = null; + public double selection_box_width = 0; + public double selection_box_height = 0; + public double selection_box_x = 0; + public double selection_box_y = 0; + public Glyph (string name, unichar unichar_code = 0) { this.name = name; this.unichar_code = unichar_code; - + add_help_lines (); left_limit = -28; @@ -154,20 +160,20 @@ public Glyph.no_lines (string name, unichar unichar_code = 0) { this.name = name; this.unichar_code = unichar_code; - } - - public Gee.ArrayList<Path> get_active_paths () { - return active_paths; } public Layer get_current_layer () { - return_val_if_fail (0 <= current_layer < layers.subgroups.size, new Layer ()); - return layers.subgroups.get (current_layer); + if (unlikely (!(0 <= current_layer < layers.objects.size))) { + warning ("Layer index out of bounds."); + return new Layer (); + } + + return layers.get_sublayers ().get (current_layer); } public void set_current_layer (Layer layer) { int i = 0; - foreach (Layer l in layers.subgroups) { + foreach (Layer l in layers.get_sublayers ()) { if (likely (l == layer)) { current_layer = i; return; @@ -176,29 +182,42 @@ } warning ("Layer is not added to glyph."); + } + + public Gee.ArrayList<SvgBird.Object> get_visible_objects () { + return LayerUtils.get_visible_objects (layers); } public Gee.ArrayList<Path> get_visible_paths () { - return layers.get_visible_paths ().paths; + return LayerUtils.get_visible_paths (layers).paths; } public PathList get_visible_path_list () { - return layers.get_visible_paths (); + return LayerUtils.get_visible_paths (layers); + } + + public Gee.ArrayList<SvgBird.Object> get_objects_in_current_layer () { + return get_current_layer ().objects.objects; } public Gee.ArrayList<Path> get_paths_in_current_layer () { - return get_current_layer ().get_all_paths ().paths; + return LayerUtils.get_all_paths (get_current_layer ()).paths; } public Gee.ArrayList<Path> get_all_paths () { - return layers.get_all_paths ().paths; + return LayerUtils.get_all_paths (layers).paths; } public void add_new_layer () { layers.add_layer (new Layer ()); - current_layer = layers.subgroups.size - 1; + current_layer = layers.objects.size - 1; } + public void add_layer (Layer layer) { + layers.add_layer (layer); + current_layer = layers.objects.size - 1; + } + public int get_layer_index (Layer layer) { return layers.index_of (layer); } @@ -238,6 +257,10 @@ public override void close () { undo_list.clear (); redo_list.clear (); + + foreach (Path path in get_all_paths ()) { + path.set_editable (false); + } } public void set_empty_ttf (bool e) { @@ -249,52 +272,74 @@ } public void clear_active_paths () { - selected_groups.clear (); active_paths.clear (); } - public void add_active_path (Layer? group, Path? p) { - Path path; - Layer g; - - if (p != null) { - path = (!) p; - + public void add_active_path (Path p) { + PathObject path = new PathObject.for_path ((!) p); + add_active_object (path); + } + + public void add_active_object (SvgBird.Object object) { + if (!active_paths_contains (object)) { + active_paths.add (object); + } + + if (object is PathObject) { + PathObject path = (PathObject) object; if (Toolbox.get_move_tool ().is_selected ()) { - if (path.stroke > 0) { - Toolbox.set_object_stroke (path.stroke); + if (path.get_path ().stroke > 0) { + Toolbox.set_object_stroke (path.get_path ().stroke); } } + + PenTool.active_path = path.get_path (); + } + } - if (!active_paths.contains (path)) { - active_paths.add (path); + public bool has_active_path_objects () { + foreach (SvgBird.Object object in active_paths) { + if (object is PathObject) { + return true; } - PenTool.active_path = path; } + + return false; + } - if (group != null) { - g = (!) group; - if (!selected_groups.contains (g)) { - selected_groups.add (g); + public bool active_paths_contains (SvgBird.Object object) { + Glyph glyph = MainWindow.get_current_glyph (); + + if (glyph.active_paths.contains (object)) { + return true; + } + + if (object is PathObject) { + PathObject path = (PathObject) object; + + foreach (SvgBird.Object active in glyph.active_paths) { + if (active is PathObject) { + PathObject path_active = (PathObject) active; + if (path_active.get_path () == path.get_path ()) { + return true; + } + } } } + + return false; } - + public void delete_background () { store_undo_state (); background_image = null; GlyphCanvas.redraw (); - } - - public Path? get_active_path () { - return_val_if_fail (active_paths.size > 0, null); - return active_paths.get (active_paths.size - 1); } public bool boundaries (out double x1, out double y1, out double x2, out double y2) { - var paths = get_all_paths (); + var objects = get_visible_objects (); - if (paths.size == 0) { + if (objects.size == 0) { x1 = 0; y1 = 0; x2 = 0; @@ -302,34 +347,14 @@ return false; } - x1 = CANVAS_MAX; - x2 = CANVAS_MIN; - y1 = CANVAS_MAX; - y2 = CANVAS_MIN; + layers.update_boundaries_for_object (); - foreach (Path p in paths) { - p.update_region_boundaries (); + x1 = layers.xmin; + x2 = layers.xmax; + y1 = layers.ymin; + y2 = layers.ymax; - if (p.points.size > 1) { - if (p.xmin < x1) { - x1 = p.xmin; - } - - if (p.xmax > x2) { - x2 = p.xmax; - } - - if (p.ymin < y1) { - y1 = p.ymin; - } - - if (p.ymax > y2) { - y2 = p.ymax; - } - } - } - - return x1 != double.MAX; + return x1 != SvgBird.Object.CANVAS_MAX; } public void selection_boundaries (out double x, out double y, out double w, out double h) { @@ -340,21 +365,21 @@ px2 = -10000; py2 = -10000; - foreach (Path p in active_paths) { - if (p.xmin < px) { - px = p.xmin; + foreach (SvgBird.Object object in active_paths) { + if (object.xmin < px) { + px = object.xmin; } - if (p.ymin < py) { - py = p.ymin; + if (object.ymin < py) { + py = object.ymin; } - if (p.xmax > px2) { - px2 = p.xmax; + if (object.xmax > px2) { + px2 = object.xmax; } - if (p.ymax > py2) { - py2 = p.ymax; + if (object.ymax > py2) { + py2 = object.ymax; } } @@ -445,11 +470,19 @@ } public virtual void add_path (Path p) { - if (layers.subgroups.size == 0) { + if (layers.objects.size == 0) { layers.add_layer (new Layer ()); } - get_current_layer ().add_path (p); + LayerUtils.add_path (get_current_layer (), p); + } + + public void add_object (SvgBird.Object object) { + if (layers.objects.size == 0) { + layers.add_layer (new Layer ()); + } + + get_current_layer ().add_object (object); } public override void selected_canvas () { @@ -485,7 +518,7 @@ if (index != "") { int i = int.parse (index); - if (0 <= i < layers.subgroups.size) { + if (0 <= i < layers.objects.size) { current_layer = i; } } @@ -610,7 +643,7 @@ public double get_left_side_bearing () { double x1, y1, x2, y2; - if (get_boundaries (out x1, out y1, out x2, out y2)) { + if (boundaries (out x1, out y1, out x2, out y2)) { return x1 - left_limit; } else { return right_limit - left_limit; @@ -620,89 +653,11 @@ public double get_right_side_bearing () { double x1, y1, x2, y2; - if (get_boundaries (out x1, out y1, out x2, out y2)) { + if (boundaries (out x1, out y1, out x2, out y2)) { return right_limit - x2; } else { return right_limit - left_limit; } - } - - public bool get_boundaries (out double x1, out double y1, - out double x2, out double y2) { - - double max_x, min_x, max_y, min_y; - PathList pl; - var paths = get_all_paths (); - - if (paths.size == 0) { - x1 = 0; - y1 = 0; - x2 = 0; - y2 = 0; - return false; - } - - max_x = CANVAS_MIN; - min_x = CANVAS_MAX; - max_y = CANVAS_MIN; - min_y = CANVAS_MAX; - - // FIXME: optimize - foreach (Path p in paths) { - - if (p.stroke > 0) { - pl = p.get_stroke_fast (); - - foreach (Path part in pl.paths) { - boundaries_for_path (part, ref min_x, ref max_x, ref min_y, ref max_y); - } - } else { - boundaries_for_path (p, ref min_x, ref max_x, ref min_y, ref max_y); - } - } - - x1 = min_x; - y1 = max_y; - x2 = max_x; - y2 = min_y; - - return max_x != CANVAS_MIN; - } - - void boundaries_for_path (Path p, ref double min_x, ref double max_x, - ref double min_y, ref double max_y) { - - double max_x2, max_y2, min_x2, min_y2; - - max_x2 = max_x; - min_x2 = min_x; - max_y2 = max_y; - min_y2 = min_y; - - p.all_of_path ((x, y, t) => { - if (x > max_x2) { - max_x2 = x; - } - - if (y > max_y2) { - max_y2 = y; - } - - if (x < min_x2) { - min_x2 = x; - } - - if (y < min_y2) { - min_y2 = y; - } - - return true; - }); - - max_x = max_x2; - min_x = min_x2; - max_y = max_y2; - min_y = min_y2; } bool has_top_line () { @@ -741,10 +696,14 @@ return; } } + } + + public void delete_object (SvgBird.Object o) { + layers.remove (o); } public void delete_path (Path p) { - layers.remove_path(p); + LayerUtils.remove_path(layers, p); } public string get_svg_data () { @@ -894,8 +853,9 @@ add_path (path); path.reopen (); path.create_list (); - - add_active_path (null, path); + + PathObject object = new PathObject.for_path (path); + add_active_object (object); } if (remaining_points.paths.size > 0) { @@ -1010,7 +970,7 @@ public void set_active_path (Path p) { p.reopen (); clear_active_paths (); - add_active_path (null, p); + add_active_object (new PathObject.for_path (p)); } /** Move view port centrum to this coordinate. */ @@ -1144,61 +1104,17 @@ return -y; } - public Layer? get_path_at (double x, double y) { - Layer? group = null; - bool found = false; - - foreach (Layer layer in get_current_layer ().subgroups) { - foreach (Path pt in layer.paths.paths) { - if (pt.is_over (x, y)) { - found = true; - group = layer; - } + public SvgBird.Object? get_object_at (double x, double y) { + Layer layer = get_current_layer (); + for (int i = layer.objects.size - 1; i >= 0; i--) { + SvgBird.Object o = layer.objects.get_object (i); + + if (o.is_over (x, y)) { + return o; } } - - if (!found) { - foreach (Path pt in get_paths_in_current_layer ()) { - if (pt.is_over (x, y)) { - Layer layer = new Layer (); - layer.is_counter = true; - layer.single_path = true; - layer.add_path (pt); - group = layer; - } - } - } - - return group; - } - - public bool select_path (double x, double y) { - Path? p = null; - bool found = false; - - foreach (Path pt in get_paths_in_current_layer ()) { - if (pt.is_over (x, y)) { - p = pt; - found = true; - } - } - - if (!KeyBindings.has_shift ()) { - clear_active_paths (); - } - - add_active_path (null, p); - - return found; - } - - public bool is_over_selected_path (double x, double y) { - foreach (Path pt in active_paths) { - if (pt.is_over (x, y)) { - return true; - } - } - return false; + + return null; } public void queue_redraw_path (Path path) { @@ -1215,48 +1131,6 @@ double xtb = -view_offset_x - xmax; redraw_area ((int)xtb - 10, (int)yta - 10, (int)(xtb - xta) + 10, (int) (yta - ytb) + 10); - } - - public Path get_closeset_path (double x, double y) { - double d; - EditPoint ep = new EditPoint (); - - Path min_point = new Path (); - double min_distance = double.MAX; - - double xt = path_coordinate_x (x); - double yt = path_coordinate_y (y); - var paths = get_visible_paths (); - - foreach (Path p in paths) { - if (p.is_over (xt, yt)) { - return p; - } - } - - foreach (Path p in paths) { - if (p.points.size == 0) continue; - - p.get_closest_point_on_path (ep, xt, yt); - d = Math.pow (ep.x - xt, 2) + Math.pow (ep.y - yt, 2); - - if (d < min_distance) { - min_distance = d; - min_point = p; - } - - } - - // a path without any editpoints - if (paths.size > 0) { - return paths.get (0); - } - - if (unlikely (min_distance == double.MAX)) { - warning (@"No path found in path_list."); - } - - return min_point; } public void move_selected_edit_point_coordinates (EditPoint selected_point, double xt, double yt) { @@ -1309,8 +1183,9 @@ return; } - foreach (Path path in active_paths) { + foreach (SvgBird.Object object in active_paths) { EditPoint p; + Path path = ((PathObject) object).get_path (); EditPoint pl = path.get_last_point (); if (pl.prev != null) { @@ -1324,7 +1199,6 @@ if (px > x) px -= tw + 60; if (py > y) py -= th + 60; - } else { px = x - 60; py = y - 60; @@ -1365,7 +1239,7 @@ } public void open_path () { - foreach (Path p in get_visible_paths ()) { + foreach (Path p in get_all_paths ()) { p.set_editable (true); p.recalculate_linear_handles (); @@ -1408,7 +1282,7 @@ } warning (@"No line with label $name found"); - return new Line ("Err"); + return new Line ("Error"); } public override void zoom_in () { @@ -1610,157 +1484,114 @@ cr.stroke (); } - /** Draw filled paths. */ - public void draw_paths (Context cr, Color? c = null) { - PathList stroke; - Color color; - bool open; - - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - if (c != null) { - color = (!) c; - } else if (p.color != null) { - color = (!) p.color; - } else { - color = Color.black (); - } - - if (p.stroke > 0) { - stroke = p.get_stroke_fast (); - draw_path_list (stroke, cr, color); - } else { - open = p.is_open (); + public void draw_layers (Context cr) { + foreach (SvgBird.Object object in layers.objects) { + if (object is Layer) { + Layer layer = (Layer) object; - if (open) { - p.close (); - p.recalculate_linear_handles (); - } - - p.draw_path (cr, this, color); - - if (open) { - p.reopen (); + if (layer.visible) { + draw_layer (cr, layer); } } } - cr.fill (); - cr.restore (); + + draw_bird_font_paths (cr); } - public void draw_path (Context cr) { - PathList stroke; - Color color; - - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - if (p.stroke > 0) { - stroke = p.get_stroke_fast (); - - if (p.is_editable ()) { - color = Theme.get_color ("Filled Stroke"); - color.a = 0.8; - } else { - color = Color.black (); + public void draw_layer (Context cr, Layer sublayers) { + foreach (SvgBird.Object object in sublayers.objects) { + if (object is EmbeddedSvg) { + EmbeddedSvg svg = (EmbeddedSvg) object; + + if (svg.visible) { + svg.draw_embedded_svg (cr); } - - draw_path_list (stroke, cr, color); } } - cr.fill (); - cr.restore (); + } + + public void draw_bird_font_paths (Context cr) { + Tool selected_tool = MainWindow.get_toolbox ().get_current_tool (); + + bool draw_control_points = (selected_tool is PenTool) + || (selected_tool is PointTool) + || (selected_tool is TrackTool) + || (selected_tool is BezierTool); + + if (!is_open ()) { + draw_control_points = false; + } - if (!(MainWindow.get_toolbox ().get_current_tool () is PenTool) - && !(MainWindow.get_toolbox ().get_current_tool () is PointTool) - && !(MainWindow.get_toolbox ().get_current_tool () is TrackTool) - && !(MainWindow.get_toolbox ().get_current_tool () is BezierTool)) { - cr.save (); - cr.new_path (); - foreach (Path p in active_paths) { - if (p.stroke > 0) { - stroke = p.get_stroke_fast (); - color = Theme.get_color ("Selected Objects"); - draw_path_list (stroke, cr, color); + bool has_path = false; + + if (!draw_control_points) { + foreach (SvgBird.Object object in get_visible_objects ()) { + if (object is PathObject + && object.style.stroke_width > 0) { + + has_path = true; + PathObject object_path = (PathObject) object; + object_path.draw_path (cr); } } - cr.fill (); - cr.restore (); + + if (has_path) { + cr.set_fill_rule (FillRule.WINDING); + cr.set_source_rgba (0, 0, 0, 1); + cr.fill (); + } } - - if (is_open () && Path.fill_open_path) { - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - if (p.stroke == 0) { - color = p.color == null ? get_path_fill_color () : (!) p.color; - p.draw_path (cr, this, color); + + has_path = false; + + if (!draw_control_points) { + foreach (SvgBird.Object object in get_visible_objects ()) { + if (object is PathObject + && object.style.stroke_width == 0) { + + has_path = true; + PathObject object_path = (PathObject) object; + object_path.draw_path (cr); } } - cr.fill (); - cr.restore (); - } - if (is_open ()) { - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - p.draw_outline (cr); - p.draw_edit_points (cr); + if (has_path) { + cr.set_fill_rule (FillRule.WINDING); + Theme.color (cr, "Objects"); + cr.fill (); } - cr.restore (); } + + if (draw_control_points) { + foreach (SvgBird.Object object in get_visible_objects ()) { + if (object is PathObject) { + PathObject object_path = (PathObject) object; + Glyph g = MainWindow.get_current_glyph (); - if (!is_open ()) { - // This was good for testing but it is way too slow: - // Svg.draw_svg_path (cr, get_svg_data (), Glyph.xc () + left, Glyph.yc () - baseline); + if (object_path.path.stroke > 0) { + cr.set_line_width (CanvasSettings.stroke_width / g.view_zoom); + PathObject.draw_path_list (object_path.path.get_stroke_fast (), cr); + Theme.color (cr, "Objects"); + cr.fill (); + } - cr.save (); - cr.new_path (); - foreach (Path p in get_visible_paths ()) { - if (p.stroke == 0) { - color = p.color == null ? Color.black () : (!) p.color; - p.draw_path (cr, this, color); + cr.set_line_width (CanvasSettings.stroke_width / g.view_zoom); + object_path.path.draw_path (cr); + object_path.path.draw_control_points (cr); } - } - cr.close_path (); - cr.fill (); - cr.restore (); - - foreach (Path p in active_paths) { - cr.save (); - cr.new_path (); - if (p.stroke == 0) { - p.draw_path (cr, this); - } - cr.close_path (); - cr.fill (); - cr.restore (); } } if (show_orientation_arrow) { - foreach (Path p in get_visible_paths ()) { - if (p.stroke > 0) { - stroke = p.get_stroke_fast (); - foreach (Path ps in stroke.paths) { - ps.draw_orientation_arrow (cr, orientation_arrow_opacity); + foreach (SvgBird.Object o in active_paths) { + if (o is PathObject) { + Path p = ((PathObject) o).get_path (); + + if (p.stroke == 0) { + p.draw_orientation_arrow (cr, orientation_arrow_opacity); } - } else { - p.draw_orientation_arrow (cr, orientation_arrow_opacity); } } - } - } - - private Color get_path_fill_color () { - return Theme.get_color ("Fill Color"); - } - - public void draw_path_list (PathList pl, Context cr, Color? c = null) { - foreach (Path p in pl.paths) { - p.draw_path (cr, this, c); } } @@ -1800,8 +1631,8 @@ } if (unlikely (Preferences.draw_boundaries)) { - foreach (Path p in get_visible_paths ()) { - p.draw_boundaries (cmp); + foreach (SvgBird.Object o in get_visible_objects ()) { + draw_boundaries (o, cmp); } } @@ -1818,22 +1649,29 @@ cmp.restore (); } - if (!is_empty ()) { - cmp.save (); - cmp.scale (view_zoom, view_zoom); - cmp.translate (-view_offset_x, -view_offset_y); - draw_path (cmp); - cmp.restore (); + cmp.save (); + cmp.scale (view_zoom, view_zoom); + cmp.translate (-view_offset_x, -view_offset_y); + draw_layers (cmp); + cmp.restore (); + + Tool selected_tool = MainWindow.get_toolbox ().get_current_tool (); + bool draw_selection = active_paths.size > 0 + && (selected_tool is MoveTool || selected_tool is ResizeTool); + + if (draw_selection) { + update_selection_boundaries (); + draw_selection_box (cmp); } - + cmp.save (); tool = MainWindow.get_toolbox ().get_current_tool (); tool.draw_action (tool, cmp, this); cmp.restore (); } - private void zoom_in_at_point (double x, double y, double amount = 15) { - int n = (int) (-amount); + private void zoom_in_at_point (double x, double y, double zoom = 15) { + int n = (int) (-zoom); zoom_at_point (x, y, n); } @@ -1878,6 +1716,37 @@ if (clear_redo) { redo_list.clear (); + } + } + + public void print_layers (Layer layer, int indent = 0) { + stdout.printf (@"Layer ($indent): $(layer.name)"); + + if (!layer.visible) { + stdout.printf (" hidden"); + } + + stdout.printf (@" $(layer.transforms) $(layer.style)"); + stdout.printf (@"\n"); + + foreach (SvgBird.Object object in layer.objects) { + if (object is Layer) { + Layer sublayer = (Layer) object; + print_layers (sublayer, indent + 1); + } else { + stdout.printf (@"$(object.to_string ()) $(object.transforms) $(object.style)"); + + if (!object.visible) { + stdout.printf (" hidden"); + } + + stdout.printf ("\n"); + + if (object is EmbeddedSvg) { + EmbeddedSvg embedded = (EmbeddedSvg) object; + print_layers (embedded.drawing.root_layer, indent + 1); + } + } } } @@ -1900,10 +1769,10 @@ g.add_line (line.copy ()); } - g.layers = layers.copy (); + g.layers = (Layer) layers.copy (); - foreach (Path p in active_paths) { - g.active_paths.add (p); + foreach (SvgBird.Object o in active_paths) { + g.active_paths.add (o); } if (background_image != null) { @@ -1973,7 +1842,11 @@ void set_glyph_data (Glyph g) { current_layer = g.current_layer; - layers = g.layers.copy (); + + layers = (Layer) g.layers.copy (); + + print ("\n\n"); + print_layers (g.layers); left_limit = g.left_limit; right_limit = g.right_limit; @@ -1990,8 +1863,8 @@ } clear_active_paths (); - foreach (Path p in g.active_paths) { - add_active_path (null, p); + foreach (SvgBird.Object p in g.active_paths) { + add_active_object (p); } redraw_area (0, 0, allocation.width, allocation.height); @@ -2391,12 +2264,12 @@ public void move_layer_up () { Layer layer = get_current_layer (); - if (current_layer + 2 <= layers.subgroups.size) { - return_if_fail (0 <= current_layer + 2 <= layers.subgroups.size); - layers.subgroups.insert (current_layer + 2, layer); + if (current_layer + 2 <= layers.objects.size) { + return_if_fail (0 <= current_layer + 2 <= layers.objects.size); + layers.objects.objects.insert (current_layer + 2, layer); - return_if_fail (0 <= current_layer + 1 < layers.subgroups.size); - layers.subgroups.remove_at (current_layer); + return_if_fail (0 <= current_layer + 1 < layers.objects.size); + layers.objects.objects.remove_at (current_layer); set_current_layer (layer); } @@ -2406,11 +2279,11 @@ Layer layer = get_current_layer (); if (current_layer >= 1) { - return_if_fail (0 <= current_layer - 1 < layers.subgroups.size); - layers.subgroups.insert (current_layer - 1, layer); + return_if_fail (0 <= current_layer - 1 < layers.objects.size); + layers.objects.objects.insert (current_layer - 1, layer); - return_if_fail (0 <= current_layer + 1 < layers.subgroups.size); - layers.subgroups.remove_at (current_layer + 1); + return_if_fail (0 <= current_layer + 1 < layers.objects.size); + layers.objects.objects.remove_at (current_layer + 1); set_current_layer (layer); } @@ -2483,8 +2356,143 @@ } return g2; + } + + public Gee.ArrayList<SvgBird.Object> get_active_objects () { + return active_paths; + } + + public Gee.ArrayList<Path> get_active_paths () { + Gee.ArrayList<Path> paths = new Gee.ArrayList<Path> (); + + foreach (SvgBird.Object object in active_paths) { + if (object is PathObject) { + paths.add (((PathObject) object).get_path ()); + } + } + + return paths; + } + + public void draw_boundaries (SvgBird.Object object, Context cr) { + double x = Glyph.reverse_path_coordinate_x (object.xmin); + double y = Glyph.reverse_path_coordinate_y (object.ymin); + double x2 = Glyph.reverse_path_coordinate_x (object.xmax); + double y2 = Glyph.reverse_path_coordinate_y (object.ymax); + + cr.save (); + + Theme.color (cr, "Default Background"); + cr.set_line_width (2); + cr.rectangle (x, y, x2 - x, y2 - y); + cr.stroke (); + + cr.restore (); + } + + void draw_selection_box (Context cr) { + double x, y, w, h; + double hook_width; + + x = reverse_path_coordinate_x (selection_box_x); + y = reverse_path_coordinate_y (selection_box_y); + w = selection_box_width / ivz (); + h = selection_box_height / ivz (); + + hook_width = w > 20 ? 10 : w * 0.2; + + update_selection_boundaries (); + cr.save (); + + // FIXME: use color theme + cr.set_source_rgba (0, 0, 0, 1); + + cr.set_line_width (1); + cr.move_to (x, y + hook_width); + cr.line_to (x, y); + cr.line_to (x + hook_width, y); + cr.stroke (); + + cr.move_to (x + w - hook_width, y); + cr.line_to (x + w, y); + cr.line_to (x + w, y + hook_width); + cr.stroke (); + + cr.move_to (x + w, y + h - hook_width); + cr.line_to (x + w, y + h); + cr.line_to (x + w - hook_width, y + h); + + cr.move_to (x + hook_width, y + h); + cr.line_to (x, y + h); + cr.line_to (x, y + h - hook_width); + + cr.stroke (); + cr.restore (); + + cr.save (); + cr.set_source_rgba (1, 1, 1, 1); + + cr.set_line_width (1); + cr.move_to (x + 1, y + hook_width); + cr.line_to (x + 1, y + 1); + cr.line_to (x + hook_width, y + 1); + cr.stroke (); + + cr.move_to (x + w - hook_width, y + 1); + cr.line_to (x + w - 1, y + 1); + cr.line_to (x + w - 1, y + hook_width); + cr.stroke (); + + cr.move_to (x + w - 1, y + h - hook_width); + cr.line_to (x + w - 1, y + h - 1); + cr.line_to (x + w - hook_width, y + h - 1); + + cr.move_to (x + hook_width, y + h - 1); + cr.line_to (x + 1, y + h - 1); + cr.line_to (x + 1, y + h - hook_width); + + cr.stroke (); + cr.restore (); + } + + public void update_selection_boundaries () { + get_selection_box_boundaries (out selection_box_x, + out selection_box_y, out selection_box_width, + out selection_box_height); + } + + public void get_selection_box_boundaries (out double x, out double y, out double w, out double h) { + double px, py, px2, py2; + + px = CANVAS_MAX; + py = CANVAS_MAX; + px2 = CANVAS_MIN; + py2 = CANVAS_MIN; + + foreach (SvgBird.Object p in get_active_objects ()) { + if (px > p.xmin) { + px = p.xmin; + } + + if (py > p.ymin) { + py = p.ymin; + } + + if (px2 < p.xmax) { + px2 = p.xmax; + } + + if (py2 < p.ymax) { + py2 = p.ymax; + } + } + + w = px2 - px; + h = py2 - py; + x = px; + y = py2; } } }
--- a/libbirdfont/GlyphCanvas.vala +++ b/libbirdfont/GlyphCanvas.vala @@ -81,7 +81,7 @@ } public void redraw_area (int x, int y, int w, int h) { - if (MenuTab.has_suppress_event ()) { + if (unlikely (MenuTab.has_suppress_event ())) { warning ("Do not call redraw from background thread."); } else { signal_redraw_area (x, y, w + (int) MainWindow.scrollbar.width, h);
--- a/libbirdfont/GlyphRange.vala +++ b/libbirdfont/GlyphRange.vala @@ -479,10 +479,9 @@ } public unichar get_character (uint32 index) { - int64 ti; - string chr; UniRange r; - unichar c; + unichar c; + string chr; UniRange? range; uint32 range_start_index;
--- a/libbirdfont/GlyphSequence.vala +++ b/libbirdfont/GlyphSequence.vala @@ -100,9 +100,18 @@ foreach (Alternate a in alternates) { GlyphSequence old = new GlyphSequence (); - Glyph? g = font.get_glyph_by_name (a.glyph_name); + string name; + Glyph? g; + + name = a.glyph_name; + + if (name == "space") { + name = " "; + } + + g = font.get_glyph_by_name (name); - if (g != null) { + if (likely (g != null)) { old.add (g); if (a.alternates.size > 0) { @@ -110,7 +119,7 @@ string alt_name = a.alternates.get (0); Glyph? alt = font.get_glyph_by_name (alt_name); - if (alt != null) { + if (likely (alt != null)) { GlyphSequence replacement = new GlyphSequence (); replacement.add (alt); ligature_sequence.replace (old, replacement);
diff --git libbirdfont/Gradient.vala(deleted)
--- a/libbirdfont/Gradient.vala +++ /dev/null @@ -1,54 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - 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 3 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. - */ - - using Cairo; - using Math; - - namespace BirdFont { - - public class Gradient : GLib.Object { - public double x1; - public double y1; - public double x2; - public double y2; - - public Gee.ArrayList<Stop> stops; - - public int id = -1; - - public Gradient () { - x1 = 0; - y1 = 0; - x2 = 0; - y2 = 0; - stops = new Gee.ArrayList<Stop> (); - } - - public Gradient copy () { - Gradient g = new Gradient (); - g.x1 = x1; - g.y1 = y1; - g.x2 = x2; - g.y2 = y2; - - foreach (Stop s in stops) { - g.stops.add (s.copy ()); - } - - return g; - } - } - - }
--- a/libbirdfont/ImportUtils.vala +++ b/libbirdfont/ImportUtils.vala @@ -34,6 +34,7 @@ BirdFont.current_font = new Font (); BirdFont.current_glyph_collection = new GlyphCollection.with_glyph ('\0', ""); MainWindow.init (); + SvgType type = SvgType.REGULAR; if (arg.length < 3) { print_import_help (arg); @@ -43,7 +44,11 @@ bf_file = build_absoulute_path (arg[1]); for (int i = 2; i < arg.length; i++) { - svg_files.add (arg[i]); + if (arg[i] == "--color") { + type = SvgType.COLOR; + } else { + svg_files.add (arg[i]); + } } bf = File.new_for_path (bf_file); @@ -79,7 +84,7 @@ foreach (string f in svg_files) { svg = File.new_for_path (f); - imported = import_svg_file (font, svg); + imported = import_svg_file (font, svg, type); if (!imported) { stdout.printf (t_("Failed to import") + " " + f + "\n"); @@ -93,7 +98,7 @@ return 0; } - public static bool import_svg_file (Font font, File svg_file) { + public static bool import_svg_file (Font font, File svg_file, SvgType type) { string file_name = (!) svg_file.get_basename (); string glyph_name; StringBuilder n; @@ -158,10 +163,14 @@ stdout.printf (@"$(glyph.version_id)"); stdout.printf ("\n"); - SvgParser.import_svg ((!) svg_file.get_path ()); + if (type == SvgType.COLOR) { + SvgParser.import_color_svg (glyph, (!) svg_file.get_path ()); + } else { + SvgParser.import_svg ((!) svg_file.get_path ()); + } return true; } }
--- a/libbirdfont/KerningDisplay.vala +++ b/libbirdfont/KerningDisplay.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2014 2015 Johan Mattsson + Copyright (C) 2012 2014 2015 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -49,6 +49,8 @@ public static bool right_to_left = false; WidgetAllocation allocation = new WidgetAllocation (); + + TextArea description; public KerningDisplay () { GlyphSequence w = new GlyphSequence (); @@ -198,14 +200,14 @@ cr.save (); glyph.add_help_lines (); - + if (right_to_left) { cr.translate (-kern + x - glyph.get_lsb () - glyph.get_width () - Glyph.xc (), glyph.get_baseline () + y - Glyph.yc ()); } else { cr.translate (kern + x - glyph.get_lsb () - Glyph.xc (), glyph.get_baseline () + y - Glyph.yc ()); } - glyph.draw_paths (cr); + glyph.draw_layers (cr); cr.restore (); w = glyph.get_width ();
--- a/libbirdfont/LabelTool.vala +++ b/libbirdfont/LabelTool.vala @@ -79,7 +79,8 @@ }); } - void clear_cache () { + public override void clear_cache () { + base.clear_cache (); selected_cache = null; deselected_cache = null; }
diff --git libbirdfont/Layer.vala(deleted)
--- a/libbirdfont/Layer.vala +++ /dev/null @@ -1,163 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - 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 3 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. - */ - - namespace BirdFont { - - public class Layer : GLib.Object { - public PathList paths; - public Gee.ArrayList<Layer> subgroups; - public bool visible = true; - public string name = "Layer"; - - public bool is_counter = false; - public Gradient? gradient = null; - public bool single_path = false; - - public Layer () { - paths = new PathList (); - subgroups = new Gee.ArrayList<Layer> (); - } - - public int index_of (Layer sublayer) { - return subgroups.index_of (sublayer); - } - - public PathList get_all_paths () { - PathList p = new PathList (); - - p.append (paths); - - foreach (Layer sublayer in subgroups) { - p.append (sublayer.get_all_paths ()); - } - - return p; - } - - public PathList get_visible_paths () { - PathList p = new PathList (); - - if (visible) { - p.append (paths); - } - - foreach (Layer sublayer in subgroups) { - if (sublayer.visible) { - p.append (sublayer.get_all_paths ()); - } - } - - return p; - } - - public void add_layer (Layer layer) { - subgroups.add (layer); - } - - public void add_path (Path path) { - paths.add (path); - } - - public void remove_path (Path path) { - paths.remove (path); - foreach (Layer sublayer in subgroups) { - sublayer.remove_path (path); - } - } - - public void remove_layer (Layer layer) { - subgroups.remove (layer); - foreach (Layer sublayer in subgroups) { - sublayer.remove_layer (layer); - } - } - - public Layer copy () { - Layer layer = new Layer (); - - layer.name = name; - layer.paths = paths.copy (); - layer.visible = visible; - - foreach (Layer l in subgroups) { - layer.subgroups.add (l.copy ()); - } - - if (gradient != null) { - layer.gradient = ((!) gradient).copy (); - } - - layer.single_path = single_path; - - return layer; - } - - public void get_boundaries (out double x, out double y, out double w, out double h) { - double px, py, px2, py2; - - px = Glyph.CANVAS_MAX; - py = Glyph.CANVAS_MAX; - px2 = Glyph.CANVAS_MIN; - py2 = Glyph.CANVAS_MIN; - - foreach (Path p in get_all_paths ().paths) { - if (px > p.xmin) { - px = p.xmin; - } - - if (py > p.ymin) { - py = p.ymin; - } - - if (px2 < p.xmax) { - px2 = p.xmax; - } - - if (py2 < p.ymax) { - py2 = p.ymax; - } - } - - w = px2 - px; - h = py2 - py; - x = px; - y = py2; - } - - public void print (int indent = 0) { - foreach (Path p in paths.paths) { - for (int i = 0; i < indent; i++) { - stdout.printf ("\t"); - } - stdout.printf (@"Path open: $(p.is_open ())"); - - if (p.color != null) { - stdout.printf (" %s", ((!) p.color).to_rgb_hex ()); - } - - stdout.printf ("\n"); - } - - foreach (Layer l in subgroups) { - for (int i = 0; i < indent; i++) { - stdout.printf ("\t"); - } - stdout.printf ("%s\n", l.name); - l.print (indent + 1); - } - } - } - - }
--- a/libbirdfont/LayerLabel.vala +++ b/libbirdfont/LayerLabel.vala @@ -13,6 +13,7 @@ */ using Cairo; + using SvgBird; namespace BirdFont { @@ -26,6 +27,8 @@ /** Add margin when layer is moves. */ bool active_layer = false; + + static TextArea? help_text_hide = null; public LayerLabel (Layer layer) { base (); @@ -39,14 +42,18 @@ panel_press_action.connect ((selected, button, tx, ty) => { if (y <= ty <= y + h) { - if (tx >= w - 30 * Toolbox.get_scale ()) { + if (tx >= w - 30) { DrawingTools.deselect_layers (); remove_layer (); - } if (tx < 25 * Toolbox.get_scale ()) { + } else if (tx < 25) { layer.visible = !layer.visible; GlyphCanvas.redraw (); BirdFont.get_current_font ().touch (); MainWindow.get_current_glyph ().clear_active_paths (); + + Glyph g = MainWindow.get_current_glyph (); + print ("\n\n"); + g.print_layers (g.layers); } else { active_layer = true; select_layer (); @@ -84,6 +91,10 @@ }); } + public override void clear_cache () { + base.clear_cache (); + } + void move_layer_up () { int i; Glyph g = MainWindow.get_current_glyph ();
diff --git libbirdfont/LayerUtils.vala(new)
--- /dev/null +++ b/libbirdfont/LayerUtils.vala @@ -1,1 +1,129 @@ + /* + Copyright (C) 2015 Johan Mattsson + + 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 3 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. + */ + + using SvgBird; + + namespace BirdFont { + + public class LayerUtils { + + public static Gee.ArrayList<SvgBird.Object> get_visible_objects (Layer layer) { + ObjectGroup group = new ObjectGroup (); + add_visible_objects (layer, group); + return group.objects; + } + + public static void add_visible_objects (Layer layer, ObjectGroup objects) { + foreach (SvgBird.Object o in layer.objects) { + if (o is Layer) { + Layer sublayer = (Layer) o; + + if (sublayer.visible) { + add_visible_objects (sublayer, objects); + } + } else { + if (o.visible) { + objects.add (o); + } + } + } + } + + public static PathList get_all_paths (Layer layer) { + PathList paths = new PathList (); + add_paths_to_group (layer, paths); + return paths; + } + + public static void add_paths_to_group (Layer layer, PathList paths) { + foreach (SvgBird.Object o in layer.objects) { + if (o is PathObject) { + PathObject p = (PathObject) o; + paths.add (p.get_path ()); + } else if (o is Layer) { + add_visible_paths_to_group ((Layer) o, paths); + } + } + } + + public static PathList get_visible_paths (Layer layer) { + PathList paths = new PathList (); + add_visible_paths_to_group (layer, paths); + return paths; + } + + public static void add_visible_paths_to_group (Layer layer, PathList paths) { + foreach (SvgBird.Object o in layer.objects) { + if (o.visible) { + if (o is PathObject) { + PathObject p = (PathObject) o; + paths.add (p.get_path ()); + } else if (o is Layer) { + add_visible_paths_to_group ((Layer) o, paths); + } + } + } + } + + public static void add_path (Layer layer, Path path) { + PathObject p = new PathObject.for_path (path); + layer.add_object (p); + } + + public static void append_paths (Layer layer, PathList path_list) { + foreach (Path p in path_list.paths) { + add_path (layer, p); + } + } + + private static PathObject? get_object_path (Layer layer, Path path) { + foreach (SvgBird.Object o in layer.objects) { + if (o is PathObject) { + PathObject p = (PathObject) o; + if (p.get_path () == path) { + return p; + } + } + } + + return null; + } + + public static void remove_path (Layer layer, Path path) { + PathObject? p = get_object_path (layer, path); + + if (p != null) { + layer.objects.remove ((!) p); + } + + foreach (SvgBird.Layer sublayer in layer.get_sublayers ()) { + remove_path (sublayer, path); + } + } + + public static PathList get_paths_in_layer (Layer layer) { + PathList paths = new PathList (); + + foreach (SvgBird.Object object in layer.objects) { + if (object is PathObject) { + paths.add (((PathObject) object).get_path ()); + } + } + + return paths; + } + } + + }
--- a/libbirdfont/LicenseDialog.vala +++ b/libbirdfont/LicenseDialog.vala @@ -28,11 +28,11 @@ static const double margin = 20; public LicenseDialog () { - agreement = new TextArea (font_size); + Color color = Theme.get_color ("Text Tool Box"); + agreement = new TextArea (font_size, color); agreement.min_width = 300; agreement.set_editable (false); agreement.draw_border = false; - agreement.text_color = Theme.get_color ("Text Tool Box"); agreement.set_text ("BirdFont is developed with donations, please consider donating to the project.\n\nThis is the freeware version of BirdFont. You may use it for creating fonts under the SIL Open Font License.\n\nWhich license do you want to use for your font?"); decline = new Button ("Commercial License");
--- a/libbirdfont/Ligatures.vala +++ b/libbirdfont/Ligatures.vala @@ -58,6 +58,10 @@ lig = new GlyphSequence (); foreach (string n in font.get_names (ligature)) { + if (n == "space") { + n = " "; + } + gc = font.get_glyph_collection_by_name (n); if (gc == null) { @@ -69,6 +73,10 @@ gs = new GlyphSequence (); foreach (string s in subst_names) { + if (s == "space") { + s = " "; + } + gc = font.get_glyph_collection_by_name (s); if (gc == null) {
--- a/libbirdfont/Line.vala +++ b/libbirdfont/Line.vala @@ -167,13 +167,13 @@ parsed_value = double.parse (submitted_value); if (lsb) { - if (glyph.get_boundaries (out x1, out y1, out x2, out y2)) { + if (glyph.boundaries (out x1, out y1, out x2, out y2)) { parsed_value = x1 - parsed_value; } else { parsed_value = glyph.right_limit - parsed_value; } } else if (rsb) { - if (glyph.get_boundaries (out x1, out y1, out x2, out y2)) { + if (glyph.boundaries (out x1, out y1, out x2, out y2)) { parsed_value += x2; } else { parsed_value = glyph.left_limit - parsed_value;
--- a/libbirdfont/MainWindow.vala +++ b/libbirdfont/MainWindow.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2014 Johan Mattsson + Copyright (C) 2012 2014 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as
--- a/libbirdfont/Menu.vala +++ b/libbirdfont/Menu.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2014 2015 Johan Mattsson + Copyright (C) 2014 - 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -285,19 +285,23 @@ }); export_menu.items.add (export_glyph); - MenuItem import_svg = add_menu_item (t_("Import SVG file"), "import svg file", "Glyph"); - import_svg.action.connect (() => { - SvgParser.import (); + MenuItem import_color_svg; + import_color_svg = add_menu_item (t_("Import SVG file"), + "import svg file color", "Glyph"); + import_color_svg.action.connect (() => { + SvgParser.import (SvgType.COLOR); show_menu = false; }); - export_menu.items.add (import_svg); - - MenuItem import_svg_folder = add_menu_item (t_("Import SVG folder"), "import svg folder", ""); - import_svg_folder.action.connect (() => { - SvgParser.import_folder (); + export_menu.items.add (import_color_svg); + + MenuItem import_svg_color_folder; + import_svg_color_folder = add_menu_item (t_("Import SVG folder"), + "import svg folder color", ""); + import_svg_color_folder.action.connect (() => { + SvgParser.import_folder (SvgType.COLOR); show_menu = false; }); - export_menu.items.add (import_svg_folder); + export_menu.items.add (import_svg_color_folder); MenuItem import_background_image = add_menu_item (t_("Import Background Image"), "import background image"); import_background_image.action.connect (() => { @@ -499,7 +503,7 @@ show_menu = false; }); menu.items.add (version); - + set_current_menu (menu); top_menu = menu;
--- a/libbirdfont/MenuItem.vala +++ b/libbirdfont/MenuItem.vala @@ -23,8 +23,8 @@ public double y; // key bindings - public uint modifiers = NONE; - public unichar key = '\0'; + public virtual uint modifiers { get; set; default = NONE; } + public virtual unichar key { get; set; default = '\0'; } public Gee.ArrayList<string> displays = new Gee.ArrayList<string> ();
--- a/libbirdfont/MenuTab.vala +++ b/libbirdfont/MenuTab.vala @@ -11,6 +11,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ + + using SvgBird; namespace BirdFont { @@ -87,7 +89,7 @@ GlyphCanvas.redraw (); return false; }); - idle.attach (null); + idle.attach (MainContext.default ()); } public static bool validate_metadata () { @@ -122,8 +124,7 @@ Font current_font = BirdFont.get_current_font (); string ttf_name = ExportSettings.get_file_name (current_font) + ".ttf"; string ttf_name_mac = ExportSettings.get_file_name_mac (current_font) + ".ttf"; - - print (@"$ttf_name == $ttf_name_mac"); + if (ttf_name == ttf_name_mac) { MainWindow.show_message (t_("You need to choose a different name for the TTF file with Mac adjustmets.")); ttf_name_mac = ExportSettings.get_file_name_mac (current_font) + " Mac.ttf"; @@ -280,6 +281,7 @@ } DrawingTools.set_stroke_tool_visibility (); + string lock_grid = f.settings.get_setting ("lock_grid"); bool lg = bool.parse (lock_grid); @@ -331,6 +333,7 @@ MainWindow.get_toolbox ().update_expanders (); MainWindow.get_toolbox ().update_all_expanders (); Toolbox.redraw_tool_box (); + OverViewItem.glyph_scale = 1; } // FIXME: background thread @@ -651,34 +654,34 @@ Gee.ArrayList<Path> paths = new Gee.ArrayList<Path> (); // selected objects - foreach (Path p in g.active_paths) { + foreach (Path p in g.get_active_paths ()) { paths.add (PenTool.simplify (p, false, PenTool.simplification_threshold)); } // selected segments if (paths.size == 0) { - foreach (Path p in g.get_all_paths ()) { - g.add_active_path (null, p); + foreach (Path p in g.get_active_paths ()) { + g.add_active_path (p); } - foreach (Path p in g.active_paths) { + foreach (Path p in g.get_active_paths ()) { paths.add (PenTool.simplify (p, true, PenTool.simplification_threshold)); } } g.store_undo_state (); - foreach (Path p in g.active_paths) { - g.layers.remove_path (p); + foreach (SvgBird.Object o in g.active_paths) { + g.layers.remove (o); } - foreach (Path p in g.active_paths) { - g.layers.remove_path (p); + foreach (Path p in g.get_active_paths ()) { + LayerUtils.remove_path (g.layers, p); } foreach (Path p in paths) { g.add_path (p); - g.add_active_path (null, p); + g.add_active_path (p); } g.active_paths.clear ();
--- a/libbirdfont/MoveTool.vala +++ b/libbirdfont/MoveTool.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2013 Johan Mattsson + Copyright (C) 2012 2013 2015 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -14,6 +14,7 @@ using Math; using Cairo; + using SvgBird; namespace BirdFont { @@ -28,10 +29,12 @@ static double selection_y = 0; static bool group_selection= false; - public static static double selection_box_width = 0; - public static static double selection_box_height = 0; - public static static double selection_box_center_x = 0; - public static static double selection_box_center_y = 0; + public static double selection_box_width = 0; + public static double selection_box_height = 0; + public static double selection_box_center_x = 0; + public static double selection_box_center_y = 0; + public static double selection_box_left = 0; + public static double selection_box_top = 0; public signal void selection_changed (); public signal void objects_moved (); @@ -94,8 +97,8 @@ g.store_undo_state (); } - foreach (Path p in g.active_paths) { - g.layers.remove_path (p); + foreach (SvgBird.Object p in g.active_paths) { + g.layers.remove (p); g.update_view (); } @@ -109,9 +112,8 @@ public void move (int x, int y) { Glyph glyph = MainWindow.get_current_glyph (); - double dx = last_x - x; - double dy = last_y - y; - double p = PenTool.precision; + double dx = Glyph.path_coordinate_x (last_x) - Glyph.path_coordinate_x (x); + double dy = Glyph.path_coordinate_y (last_y) - Glyph.path_coordinate_y (y); double delta_x, delta_y; if (!move_path) { @@ -121,21 +123,11 @@ if (move_path && (fabs(dx) > 0 || fabs (dy) > 0)) { moved = true; - delta_x = Glyph.ivz () * -dx * p; - delta_y = Glyph.ivz () * dy * p; - - foreach (Layer group in glyph.selected_groups) { - if (group.gradient != null) { - Gradient g = (!) group.gradient; - g.x1 += delta_x; - g.x2 += delta_x; - g.y1 += delta_y; - g.y2 += delta_y; - } - } + delta_x = -dx; + delta_y = -dy; - foreach (Path path in glyph.active_paths) { - path.move (delta_x, delta_y); + foreach (SvgBird.Object object in glyph.active_paths) { + object.move (delta_x, delta_y); } } @@ -162,7 +154,7 @@ if (GridTool.is_visible () && moved) { tie_paths_to_grid (glyph); } else if (GridTool.has_ttf_grid ()) { - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { tie_path_to_ttf_grid (p); } } @@ -177,10 +169,12 @@ if (glyph.active_paths.size > 0) { selection_changed (); objects_moved (); - DrawingTools.resize_tool.signal_objects_rotated (); - foreach (Path p in glyph.active_paths) { - p.create_full_stroke (); + foreach (SvgBird.Object o in glyph.active_paths) { + if (o is PathObject) { + PathObject path = (PathObject) o; + path.get_path ().create_full_stroke (); + } } } else { objects_deselected (); @@ -189,42 +183,36 @@ public void press (int b, int x, int y) { Glyph glyph = MainWindow.get_current_glyph (); - Path p; + SvgBird.Object object; bool selected = false; - Layer? group; - Layer g; + SvgBird.Object? o; - glyph.store_undo_state (); - group_selection = false; - - group = glyph.get_path_at (x, y); - - if (group != null) { - g = (!) group; - return_if_fail (g.paths.paths.size > 0); - p = g.paths.paths.get (0); - selected = glyph.active_paths.contains (p); - + glyph.store_undo_state (); + double px = Glyph.path_coordinate_x (x); + double py = Glyph.path_coordinate_y (y); + o = glyph.get_object_at (px, py); + + if (o != null) { + object = (!) o; + selected = glyph.active_paths_contains (object); + if (!selected && !KeyBindings.has_shift ()) { glyph.clear_active_paths (); } - foreach (Path lp in g.paths.paths) { - if (selected && KeyBindings.has_shift ()) { - glyph.selected_groups.remove ((!) group); - glyph.active_paths.remove (lp); - } else { - glyph.add_active_path ((!) group, lp); - } - } + if (selected && KeyBindings.has_shift ()) { + glyph.active_paths.remove (object); + } else { + glyph.add_active_object (object); + } } else if (!KeyBindings.has_shift ()) { glyph.clear_active_paths (); } - - move_path = true; update_selection_boundaries (); - + + move_path = true; + last_x = x; last_y = y; @@ -238,8 +226,7 @@ selection_changed (); GlyphCanvas.redraw (); } - - + void select_group () { double x1 = Glyph.path_coordinate_x (Math.fmin (selection_x, last_x)); double y1 = Glyph.path_coordinate_y (Math.fmin (selection_y, last_y)); @@ -249,10 +236,10 @@ glyph.clear_active_paths (); - foreach (Path p in glyph.get_paths_in_current_layer ()) { + foreach (SvgBird.Object p in glyph.get_objects_in_current_layer ()) { if (p.xmin > x1 && p.xmax < x2 && p.ymin < y1 && p.ymax > y2) { - if (p.points.size > 0) { - glyph.add_active_path (null, p); + if (!p.is_empty ()) { + glyph.add_active_object (p); } } } @@ -260,46 +247,32 @@ selection_changed (); } - public static void update_selection_boundaries () { + public static void update_selection_boundaries () { get_selection_box_boundaries (out selection_box_center_x, out selection_box_center_y, out selection_box_width, - out selection_box_height); + out selection_box_height, out selection_box_left, + out selection_box_top); } public void move_to_baseline () { Glyph glyph = MainWindow.get_current_glyph (); Font font = BirdFont.get_current_font (); - double x, y, w, h; + double x, y, w, h, l, t; - get_selection_box_boundaries (out x, out y, out w, out h); + get_selection_box_boundaries (out x, out y, out w, out h, out l, out t); - foreach (Path path in glyph.active_paths) { + foreach (SvgBird.Object path in glyph.active_paths) { path.move (glyph.left_limit - x + w / 2, font.base_line - y + h / 2); } update_selection_boundaries (); objects_moved (); GlyphCanvas.redraw (); - } - - static void draw_selection_box (Context cr) { - double x = Math.fmin (selection_x, last_x); - double y = Math.fmin (selection_y, last_y); - - double w = Math.fabs (selection_x - last_x); - double h = Math.fabs (selection_y - last_y); - - cr.save (); - - Theme.color (cr, "Foreground 1"); - cr.set_line_width (2); - cr.rectangle (x, y, w, h); - cr.stroke (); - - cr.restore (); } - public static void get_selection_box_boundaries (out double x, out double y, out double w, out double h) { + public static void get_selection_box_boundaries (out double x, out double y, out double w, out double h, + out double left, out double top) { + double px, py, px2, py2; Glyph glyph = MainWindow.get_current_glyph (); @@ -307,24 +280,32 @@ py = 10000; px2 = -10000; py2 = -10000; - - foreach (Path p in glyph.active_paths) { - p.update_region_boundaries (); + top = 10000; + left = 10000; + + foreach (SvgBird.Object o in glyph.active_paths) { + if (top > o.top) { + top = o.top; + } + + if (left > o.left) { + left = o.left; + } - if (px > p.xmin) { - px = p.xmin; + if (px > o.xmin) { + px = o.xmin; } - if (py > p.ymin) { - py = p.ymin; + if (py > o.ymin) { + py = o.ymin; } - if (px2 < p.xmax) { - px2 = p.xmax; + if (px2 < o.xmax) { + px2 = o.xmax; } - if (py2 < p.ymax) { - py2 = p.ymax; + if (py2 < o.ymax) { + py2 = o.ymax; } } @@ -358,7 +339,7 @@ break; } - foreach (Path path in glyph.active_paths) { + foreach (SvgBird.Object path in glyph.active_paths) { path.move (x * Glyph.ivz (), y * Glyph.ivz ()); } @@ -369,7 +350,7 @@ GlyphCanvas.redraw (); } - static void tie_path_to_ttf_grid (Path p) { + static void tie_path_to_ttf_grid (SvgBird.Object p) { double sx, sy, qx, qy; sx = p.xmax; @@ -419,7 +400,7 @@ dx_min = Math.fabs (qx - minx); dx_max = Math.fabs (sx - maxx); - foreach (Path p in g.active_paths) { + foreach (SvgBird.Object p in g.active_paths) { if (dy_min < dy_max) { p.move (0, qy - miny); } else { @@ -438,51 +419,64 @@ public static void update_boundaries_for_selection () { Glyph glyph = MainWindow.get_current_glyph (); - foreach (Path p in glyph.active_paths) { - p.update_region_boundaries (); - } + glyph.layers.update_boundaries_for_object (); } public static void flip_vertical () { - flip (true); + DrawingTools.move_tool.flip (true); } public static void flip_horizontal () { - flip (false); + DrawingTools.move_tool.flip (false); } - public static void flip (bool vertical) { - double xc, yc, xc2, yc2, w, h; - double dx, dy; + public void flip (bool vertical) { Glyph glyph = MainWindow.get_current_glyph (); - update_selection_boundaries (); + update_selection_boundaries (); - xc = selection_box_center_x; - yc = selection_box_center_y; + foreach (SvgBird.Object object in glyph.active_paths) { + Matrix matrix = Matrix.identity (); + double x = 0; + double y = 0; + + if (object is EmbeddedSvg) { + EmbeddedSvg svg = (EmbeddedSvg) object; + x = selection_box_left - svg.x + selection_box_width / 2; + y = selection_box_top + svg.y + selection_box_height / 2; + } else { + x = selection_box_center_x; + y = selection_box_center_y; + } + + matrix.translate (x, y); - foreach (Path p in glyph.active_paths) { if (vertical) { - p.flip_vertical (); + matrix.scale (1, -1); } else { - p.flip_horizontal (); + matrix.scale (-1, 1); } + + matrix.translate (-x, -y); - p.reverse (); + SvgTransform transform = new SvgTransform.for_matrix (matrix); + object.transforms.add (transform); + object.transforms.collapse_transforms (); + + if (object is PathObject) { + Path path = ((PathObject) object).get_path (); + Matrix m = object.transforms.get_matrix (); + object.transforms.clear (); + path.transform (m); + path.reverse (); + object.move (0, 0); + } } - get_selection_box_boundaries (out xc2, out yc2, out w, out h); - - dx = -(xc2 - xc); - dy = -(yc2 - yc); - - foreach (Path p in glyph.active_paths) { - p.move (dx, dy); - } - update_selection_boundaries (); - PenTool.reset_stroke (); + objects_moved (); + PenTool.reset_stroke (); BirdFont.get_current_font ().touch (); } @@ -490,9 +484,9 @@ Glyph g = MainWindow.get_current_glyph (); g.clear_active_paths (); - foreach (Path p in g.get_paths_in_current_layer ()) { - if (p.points.size > 0) { - g.add_active_path (null, p); + foreach (SvgBird.Object p in g.get_objects_in_current_layer ()) { + if (!p.is_empty ()) { + g.add_active_object (p); } } @@ -500,8 +494,74 @@ update_selection_boundaries (); objects_moved (); + + ResizeTool.update_selection_box (); + } + + static void draw_selection_box (Context cr) { + double x = Math.fmin (selection_x, last_x); + double y = Math.fmin (selection_y, last_y); + + double w = Math.fabs (selection_x - last_x); + double h = Math.fabs (selection_y - last_y); + + cr.save (); + Theme.color (cr, "Foreground 1"); + cr.set_line_width (2); + cr.rectangle (x, y, w, h); + cr.stroke (); + cr.restore (); + } + + public void convert_svg_to_monochrome () { + ObjectGroup embedded_paths; + Glyph glyph = MainWindow.get_current_glyph (); + + embedded_paths = new ObjectGroup (); + + foreach (SvgBird.Object object in glyph.active_paths) { + if (object is EmbeddedSvg) { + embedded_paths.add (object); + } + } + + convert_objects_to_monochrome_glyph (glyph, embedded_paths); + GlyphCanvas.redraw (); + } + + public void convert_glyph_to_monochrome (Glyph glyph) { + ObjectGroup embedded_paths; + + embedded_paths = new ObjectGroup (); + + foreach (SvgBird.Object object in glyph.get_visible_objects ()) { + if (object is EmbeddedSvg) { + embedded_paths.add (object); + } + } + + convert_objects_to_monochrome_glyph (glyph, embedded_paths); + } + + public void convert_objects_to_monochrome_glyph (Glyph glyph, ObjectGroup embedded_paths) { + Font font = BirdFont.get_current_font (); + + foreach (SvgBird.Object object in embedded_paths) { + if (object is EmbeddedSvg) { + EmbeddedSvg svg = (EmbeddedSvg) object; + glyph.clear_active_paths (); + string transformed_svg_data = svg.get_transformed_svg_data (); + SvgParser svg_parser = new SvgParser (); + PathList path_list = svg_parser.import_svg_data_in_glyph (transformed_svg_data, glyph); + glyph.delete_object (svg); + + foreach (Path path in path_list.paths) { + path.move (svg.x - glyph.left_limit, svg.y - font.top_limit); + } + } + } } } }
--- a/libbirdfont/OpenFontFormat/ContextualLigature.vala +++ b/libbirdfont/OpenFontFormat/ContextualLigature.vala @@ -169,6 +169,11 @@ gs = new GlyphSequence (); foreach (string s in font.get_names (context)) { + + if (s == "space") { + s = " "; + } + gc = font.get_glyph_collection_by_name (s); if (gc == null) {
--- a/libbirdfont/OpenFontFormat/DirectoryTable.vala +++ b/libbirdfont/OpenFontFormat/DirectoryTable.vala @@ -33,6 +33,7 @@ public Os2Table os_2_table; public PostTable post_table; public LocaTable loca_table; + public SvgTable svg_table; public OffsetTable offset_table; @@ -57,6 +58,7 @@ name_table = new NameTable (); os_2_table = new Os2Table (glyf_table, hmtx_table, hhea_table); post_table = new PostTable (glyf_table); + svg_table = new SvgTable (); id = "Directory table"; @@ -81,6 +83,7 @@ post_table.process (); kern_table.process (); gpos_table.process (glyf_table); + svg_table.process (glyf_table); offset_table.process (); process_directory (); // this table @@ -101,6 +104,10 @@ tables.add (gsub_table); tables.add (os_2_table); + + if (svg_table.has_glyphs ()) { + tables.add (svg_table); + } // tables.append (gdef_table); // invalid table
--- a/libbirdfont/OpenFontFormat/FontData.vala +++ b/libbirdfont/OpenFontFormat/FontData.vala @@ -14,7 +14,7 @@ namespace BirdFont { - public class FontData : Object { + public class FontData : GLib.Object { // Read pointer uint rp = 0;
--- a/libbirdfont/OpenFontFormat/GlyfData.vala +++ b/libbirdfont/OpenFontFormat/GlyfData.vala @@ -16,7 +16,7 @@ namespace BirdFont { - public class CoordinateFlags { + public class CoordinateFlags : GLib.Object { /** TTF coordinate flags. */ public static const uint8 NONE = 0;
--- a/libbirdfont/OpenFontFormat/GlyfTable.vala +++ b/libbirdfont/OpenFontFormat/GlyfTable.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012, 2013, 2014 Johan Mattsson + Copyright (C) 2012 2013 2014 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -165,7 +165,7 @@ g.remove_empty_paths (); unassigned = gc.is_unassigned (); - if (unassigned) { + if (unassigned && gc.get_name () != ".notdef") { unassigned_glyphs.add (gc); } @@ -281,7 +281,6 @@ // flags nflags = glyf_data.get_nflags (); if (unlikely (nflags != npoints)) { - print ("glyf table data:\n"); fd.dump (); warning (@"(nflags != npoints) ($nflags != $npoints) in glyph $(g.name). ncontours: $ncontours"); } @@ -497,7 +496,7 @@ end_points = new uint16[ncontours + 1]; for (int i = 0; i < ncontours; i++) { - end_points[i] = dis.read_ushort (); // FIXA: mind shot vector is negative + end_points[i] = dis.read_ushort (); // FIXME: mind shot vector is negative if (i > 0 && end_points[i] < end_points[i -1]) { warning (@"Next endpoint has bad value in $(name.str). (end_points[i] > end_points[i -1]) ($(end_points[i]) > $(end_points[i -1])) i: $i ncontours: $ncontours");
--- a/libbirdfont/OpenFontFormat/HheaTable.vala +++ b/libbirdfont/OpenFontFormat/HheaTable.vala @@ -111,8 +111,7 @@ FontData fd = new FontData (); Fixed version = 1 << 16; Font font = OpenFontFormatWriter.get_current_font (); - int upm, total_height; - + fd.add_fixed (version); // table version ascender = (int16) rint (font.top_limit * HeadTable.UNITS);
--- a/libbirdfont/OpenFontFormat/OpenFontFormatReader.vala +++ b/libbirdfont/OpenFontFormat/OpenFontFormatReader.vala @@ -58,7 +58,7 @@ PARSE } - public class OpenFontFormatReader : Object { + public class OpenFontFormatReader : GLib.Object { public DirectoryTable directory_table; public FontData font_data = new FontData ();
--- a/libbirdfont/OpenFontFormat/OpenFontFormatWriter.vala +++ b/libbirdfont/OpenFontFormat/OpenFontFormatWriter.vala @@ -14,7 +14,7 @@ namespace BirdFont { - public class OpenFontFormatWriter : Object { + public class OpenFontFormatWriter : GLib.Object { DataOutputStream os; DataOutputStream os_mac;
--- a/libbirdfont/OpenFontFormat/OtfInputStream.vala +++ b/libbirdfont/OpenFontFormat/OtfInputStream.vala @@ -15,7 +15,7 @@ namespace BirdFont { /** Reader for otf data types. */ - public class OtfInputStream : Object { + public class OtfInputStream : GLib.Object { public FileInputStream fin; public DataInputStream din;
--- a/libbirdfont/OpenFontFormat/OtfTable.vala +++ b/libbirdfont/OpenFontFormat/OtfTable.vala @@ -14,7 +14,7 @@ namespace BirdFont { - public class OtfTable : Object { + public class OtfTable : GLib.Object { public string id = "NO_ID";
--- a/libbirdfont/OpenFontFormat/PostTable.vala +++ b/libbirdfont/OpenFontFormat/PostTable.vala @@ -1101,8 +1101,6 @@ assert (names.size == 0); add_standard_names (); - - print ("Adding post names\n"); for (int i = 1; i < glyf_table.glyphs.size; i++) { gc = glyf_table.glyphs.get (i);
--- /dev/null +++ b/libbirdfont/OpenFontFormat/SvgTable.vala @@ -1,1 +1,399 @@ + /* + Copyright (C) 2015 Johan Mattsson + + 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 3 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. + */ + + using B; + using SvgBird; + + namespace BirdFont { + + public class SvgTable : OtfTable { + + int glyphs_in_table = 0; + Gee.ArrayList<SvgTableEntry> entries; + + public SvgTable () { + id = "SVG "; + entries = new Gee.ArrayList<SvgTableEntry> (); + } + + public bool has_glyphs () { + return glyphs_in_table > 0; + } + + public void process (GlyfTable glyf_table) throws GLib.Error { + Font font = OpenFontFormatWriter.get_current_font (); + GlyphCollection? glyph_collection; + GlyphCollection glyphs; + int gid; + Gee.ArrayList<EmbeddedSvg> embedded_svg; + + for (int index = 0; index < font.length (); index++) { + glyph_collection = font.get_glyph_collection_index (index); + + if (glyph_collection != null) { + glyphs = (!) glyph_collection; + embedded_svg = get_embedded_svg (glyphs); + + if (embedded_svg.size > 0) { + gid = glyf_table.get_gid (glyphs.get_name ()); + + StringBuilder svg = new StringBuilder (); + svg.append ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); + + svg.append ("""<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">"""); + svg.append ("\n\n"); + + svg.append ("<g id="); + svg.append ("\""); + svg.append ("glyph"); + svg.append (@"$gid"); + svg.append ("\" >"); + + foreach (EmbeddedSvg embedded in embedded_svg) { + svg.append ("<g "); + + // scale the internal coordinates from 100 units per em to the + // number of units per em in this font and move the glyph + // in to the em box + Glyph glyph = glyphs.get_current (); + double scale = HeadTable.UNITS; + double x = embedded.x - glyph.left_limit; + double y = font.base_line - embedded.y; + svg.append (@"transform=\"scale($scale) translate($x, $y)\""); + + svg.append (">"); + svg.append ("\n\n"); + + append_svg_glyph (svg, embedded, glyphs); + + svg.append ("</g>\n"); + svg.append ("\n\n"); + } + + svg.append ("</g>\n"); + svg.append ("</svg>"); + + SvgTableEntry entry; + entry = new SvgTableEntry ((uint16) gid, svg.str); + entries.add (entry); + + glyphs_in_table++; + } + } + } + + process_svg_data (); + } + + Gee.ArrayList<EmbeddedSvg> get_embedded_svg (GlyphCollection glyphs) { + Gee.ArrayList<EmbeddedSvg> svg = new Gee.ArrayList<EmbeddedSvg> (); + PathList path_list = new PathList (); + + foreach (SvgBird.Object object in glyphs.get_current ().get_visible_objects ()) { + if (object is EmbeddedSvg) { + svg.add ((EmbeddedSvg) object); + } else if (object is PathObject) { + Path path = ((PathObject) object).get_path (); + + if (path.stroke > 0) { + path_list.append (path.get_completed_stroke ()); + } else { + path_list.add (path); + } + } + } + + if (path_list.paths.size > 0) { + EmbeddedSvg svg_data = generate_svg (path_list, glyphs); + svg.add (svg_data); + } + + return svg; + } + + void append_svg_glyph (StringBuilder svg, EmbeddedSvg embedded, GlyphCollection glyphs) { + Gee.ArrayList<Tag> layer_content; + Gee.ArrayList<Tag> svg_tags; + Gee.ArrayList<Tag> meta; + XmlParser xml; + Tag svg_root_tag; + Font font; + string svg_data; + + svg_data = embedded.get_transformed_svg_data (); + font = OpenFontFormatWriter.get_current_font (); + + layer_content = new Gee.ArrayList<Tag> (); + svg_tags = new Gee.ArrayList<Tag> (); + meta = new Gee.ArrayList<Tag> (); + xml = new XmlParser (svg_data); + + if (!xml.validate ()) { + warning("Invalid SVG data in TTF table."); + return; + } + + svg_root_tag = xml.get_root_tag (); + + foreach (Tag tag in svg_root_tag) { + string name = tag.get_name(); + + if (name == "defs") { + svg_tags.add (tag); + } else if (name == "style") { + svg_tags.add (tag); + } else if (name == "metadata") { + meta.add (tag); + } else if (name == "sodipodi") { + meta.add (tag); + } else { + layer_content.add (tag); + } + } + + svg.append ("<"); + svg.append (svg_root_tag.get_name ()); + svg.append (" "); + append_tag_attributes (svg, svg_root_tag); + svg.append (">"); + + foreach (Tag tag in svg_tags) { + append_tag (svg, tag); + } + + foreach (Tag tag in layer_content) { + append_tag (svg, tag); + } + + svg.append ("</"); + svg.append (svg_root_tag.get_name ()); + svg.append (">\n"); + } + + public void append_tag (StringBuilder svg, Tag tag) { + string content = tag.get_content (); + + svg.append ("<"); + svg.append (tag.get_name ()); + + svg.append (" "); + append_tag_attributes (svg, tag); + + if (content == "") { + svg.append (" /"); + } + + svg.append (">"); + + if (content != "") { + svg.append (content); + svg.append ("</"); + svg.append (tag.get_name ()); + svg.append (">"); + } + } + + public void append_tag_attributes (StringBuilder svg, Tag tag) { + bool first = true; + + foreach (Attribute attribute in tag.get_attributes ()) { + string ns = attribute.get_namespace (); + + if (!first) { + svg.append (" "); + } + + if (ns != "") { + svg.append (ns); + svg.append (":"); + } + + svg.append (attribute.get_name ()); + svg.append ("="); + svg.append ("\""); + svg.append (attribute.get_content ()); + svg.append ("\""); + + first = false; + } + } + + public void process_svg_data () throws GLib.Error { + FontData fd = new FontData (); + + int32 svg_index_offset = 10; + + fd.add_ushort (0); // version + fd.add_ulong (svg_index_offset); + fd.add_ulong (0); // reserved + + uint32 document_offset = 2 + 12 * entries.size; + + // SVG Documents Index + fd.add_ushort ((uint16) entries.size); + + foreach (SvgTableEntry entry in entries) { + fd.add_ushort (entry.glyph_id); // start + fd.add_ushort (entry.glyph_id); // end + fd.add_ulong (document_offset); // offset + fd.add_ulong (entry.data.length_with_padding ()); // length + document_offset += entry.data.length_with_padding (); + } + + foreach (SvgTableEntry entry in entries) { + fd.append (entry.data); + } + + fd.pad (); + + this.font_data = fd; + } + + public EmbeddedSvg generate_svg (PathList paths, GlyphCollection glyphs) { + StringBuilder svg = new StringBuilder (); + + svg.append ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); + svg.append ("""<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">"""); + svg.append ("\n"); + + Glyph g = glyphs.get_current (); + Font font = OpenFontFormatWriter.get_current_font (); + svg.append (@"<g transform=\"translate(0, $(font.get_height ()))\">\n"); + svg.append (@"<g transform=\"translate($(-g.left_limit), $(-font.base_line))\">\n"); + + svg.append ("""<path style="fill:#000000;fill-opacity:1;" """); + svg.append ("d=\""); + + foreach (Path p in paths.paths) { + p.add_hidden_double_points (); + + int i = 0; + EditPoint? n = null; + EditPoint m; + + if (p.points.size < 2) { + return new EmbeddedSvg (new SvgDrawing ()); + } + + p.create_list (); + + foreach (var e in p.points) { + if (i == 0) { + add_abs_start (e, svg); + i++; + n = e; + continue; + } + + m = (!) n; + + add_abs_next (m, e, svg); + + n = e; + i++; + } + + if (!p.is_open ()) { + m = p.get_first_point (); + add_abs_next ((!) n, m, svg); + close_path (svg); + } + } + svg.append ("\" />\n"); + + svg.append ("</g>\n"); + svg.append ("</g>\n"); + svg.append ("</svg>\n"); + + EmbeddedSvg embedded = new EmbeddedSvg (new SvgDrawing ()); + embedded.x = g.left_limit; + embedded.y = font.get_height (); + embedded.svg_data = svg.str; + + return embedded; + } + + public static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg) { + if (start.right_handle.type == PointType.LINE_QUADRATIC) { + add_abs_line_to (start, end, svg); + } else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_CUBIC) { + add_abs_line_to (start, end, svg); + } else if (end.left_handle.type == PointType.QUADRATIC || start.right_handle.type == PointType.QUADRATIC) { + add_quadratic_abs_path (start, end, svg); + } else if (end.left_handle.type == PointType.DOUBLE_CURVE || start.right_handle.type == PointType.DOUBLE_CURVE) { + add_double_quadratic_abs_path (start, end, svg); + } else { + add_cubic_abs_path (start, end, svg); + } + } + + private static void add_abs_start (EditPoint ep, StringBuilder svg) { + svg.append_printf ("M"); + svg.append_printf ("%s ", round (ep.x)); + svg.append_printf ("%s ", round (-ep.y)); + } + + public static void close_path (StringBuilder svg) { + svg.append ("z"); + } + + private static void add_abs_line_to (EditPoint start, EditPoint stop, StringBuilder svg) { + svg.append ("L"); + svg.append_printf ("%s ", round (stop.x)); + svg.append_printf ("%s ", round (-stop.y)); + } + + private static void add_double_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg) { + EditPoint middle; + double x, y; + + x = start.get_right_handle ().x + (end.get_left_handle ().x - start.get_right_handle ().x) / 2; + y = start.get_right_handle ().y + (end.get_left_handle ().y - start.get_right_handle ().y) / 2; + + middle = new EditPoint (x, y, PointType.QUADRATIC); + middle.right_handle = end.get_left_handle ().copy (); + + add_quadratic_abs_path (start, middle, svg); + add_quadratic_abs_path (middle, end, svg); + } + + private static void add_quadratic_abs_path (EditPoint start, EditPoint stop, StringBuilder svg) { + // cubic path + svg.append_printf ("Q"); + + svg.append_printf ("%s ", round (start.get_right_handle ().x)); + svg.append_printf ("%s ", round (-start.get_right_handle ().y)); + + svg.append_printf ("%s ", round (stop.x)); + svg.append_printf ("%s ", round (-stop.y)); + } + + private static void add_cubic_abs_path (EditPoint start, EditPoint stop, StringBuilder svg) { + // cubic path + svg.append_printf ("C"); + + svg.append_printf ("%s ", round (start.get_right_handle ().x)); + svg.append_printf ("%s ", round (-start.get_right_handle ().y)); + + svg.append_printf ("%s ", round (stop.get_left_handle ().x)); + svg.append_printf ("%s ", round (-stop.get_left_handle ().y)); + + svg.append_printf ("%s ", round (stop.x)); + svg.append_printf ("%s ", round (-stop.y)); + } + + } + + }
--- /dev/null +++ b/libbirdfont/OpenFontFormat/SvgTableEntry.vala @@ -1,1 +1,29 @@ + /* + Copyright (C) 2015 Johan Mattsson + + 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 3 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. + */ + + namespace BirdFont { + + public class SvgTableEntry : GLib.Object { + public FontData data; + public uint16 glyph_id; + + public SvgTableEntry (uint16 gid, string svg) { + glyph_id = gid; + data = new FontData (); + data.add_str (svg); + } + } + + }
--- a/libbirdfont/OrientationTool.vala +++ b/libbirdfont/OrientationTool.vala @@ -30,8 +30,11 @@ select_action.connect ((self) => { Glyph g = MainWindow.get_current_glyph (); - foreach (Path p in g.active_paths) { - p.reverse (); + foreach (Object o in g.active_paths) { + if (o is PathObject) { + PathObject p = (PathObject) o; + p.get_path ().reverse (); + } } count_down = true; @@ -54,13 +57,17 @@ bool has_clockwise_paths = false; bool has_counter_clockwise_paths = false; - foreach (Path p in glyph.active_paths) { - if (p.is_clockwise ()) { - has_clockwise_paths = true; - } - - if (!p.is_clockwise ()) { - has_counter_clockwise_paths = true; + foreach (Object o in glyph.active_paths) { + if (o is PathObject) { + Path p = ((PathObject) o).get_path (); + + if (p.is_clockwise ()) { + has_clockwise_paths = true; + } + + if (!p.is_clockwise ()) { + has_counter_clockwise_paths = true; + } } }
--- a/libbirdfont/OtfFeatureTable.vala +++ b/libbirdfont/OtfFeatureTable.vala @@ -60,7 +60,7 @@ public override void selected_row (Row row, int column, bool delete_button) { int row_index = row.get_index (); - Object o; + GLib.Object o; String s; AlternateItem a;
--- a/libbirdfont/OverView.vala +++ b/libbirdfont/OverView.vala @@ -13,6 +13,7 @@ */ using Cairo; + using SvgBird; namespace BirdFont { @@ -93,7 +94,7 @@ TabBar tabs = MainWindow.get_tab_bar (); string n = glyph_collection.get_current ().name; bool selected = tabs.select_char (n); - Glyph g = glyph_collection.get_current (); + GlyphTab glyph_tab; if (!selected) { @@ -101,6 +102,13 @@ tabs.add_tab (glyph_tab, true, glyph_collection); set_glyph_zoom (glyph_collection); PenTool.update_orientation (); + } + + if (glyph_collection.get_name () == ".notdef") { + Font font = BirdFont.get_current_font (); + if (!font.has_glyph (".notdef")) { + font.add_glyph_collection (glyph_collection); + } } }); @@ -400,7 +408,6 @@ KeyBindings.set_require_modifier (true); update_scrollbar (); update_zoom_bar (); - OverViewItem.glyph_scale = 1; update_item_list (); selected_item = get_selected_item (); GlyphCanvas.redraw (); @@ -516,7 +523,7 @@ return new OverViewItem (); } - if (!(0 <= selected < visible_items.size)) { + if (!(0 <= selected < visible_items.size)) { return selected_item; } @@ -545,7 +552,7 @@ public void process_item_list_update () { string character_string; - Font f = BirdFont.get_current_font (); + Font font = BirdFont.get_current_font (); GlyphCollection? glyphs = null; uint32 index; OverViewItem item; @@ -569,10 +576,11 @@ y = OverViewItem.margin; if (all_available) { - uint font_length = f.length (); + uint font_length = font.length (); + int i; - for (int i = 0; i < item_list_length && index < font_length; i++) { - glyphs = f.get_glyph_collection_index ((uint32) index); + for (i = 0; i < item_list_length && index < font_length; i++) { + glyphs = font.get_glyph_collection_index ((uint32) index); return_if_fail (glyphs != null); glyph = ((!) glyphs).get_current (); @@ -582,10 +590,20 @@ item = new OverViewItem (); item.set_character (character); item.set_glyphs (glyphs); - item.x = x; - item.y = y; + item.generate_graphics (); + item.selected = (selected_items.index_of ((!) glyphs) != -1); + visible_items.add (item); index++; + } + + if (i < item_list_length && !font.has_glyph (".notdef")) { + item = new OverViewItem (); + item.set_glyphs (font.get_notdef_character ()); + item.generate_graphics (false); + item.version_menu = null; + item.selected = (selected_items.index_of ((!) glyphs) != -1); + visible_items.add (item); } } else { uint32 glyph_range_size = glyph_range.get_length (); @@ -613,8 +631,17 @@ visible_size = visible_items.size; for (int i = 0; i < visible_size; i++) { item = visible_items.get (i); - glyphs = f.get_glyph_collection_by_name ((!) item.character.to_string ()); + glyphs = font.get_glyph_collection_by_name ((!) item.character.to_string ()); item.set_glyphs (glyphs); + + if (glyphs != null) { + item.selected = (selected_items.index_of ((!) glyphs) != -1); + } + } + + for (int i = 0; i < visible_size; i++) { + item = visible_items.get (i); + item.generate_graphics (); } } @@ -622,30 +649,23 @@ y = OverViewItem.margin; visible_size = visible_items.size; - int selected_index; - bool selected_item; double full_width = OverViewItem.full_width (); for (int i = 0; i < visible_size; i++) { item = visible_items.get (i); - selected_item = false; - if (all_available) { - glyphs = f.get_glyph_collection_index ((uint32) i); + glyphs = font.get_glyph_collection_index ((uint32) i); } else { - glyphs = f.get_glyph_collection_by_name ((!) item.character.to_string ()); + glyphs = font.get_glyph_collection_by_name ((!) item.character.to_string ()); } if (glyphs != null) { - selected_index = selected_items.index_of ((!) glyphs); - selected_item = (selected_index != -1); + int selected_index = selected_items.index_of ((!) glyphs); + item.selected = (selected_index != -1); } item.adjust_scale (); - - selected_item |= (i == selected); - item.selected = selected_item; item.x = x + view_offset_x; item.y = y + view_offset_y; @@ -658,6 +678,7 @@ } } + get_selected_item ().selected = true; update_scheduled = false; } @@ -694,9 +715,8 @@ void draw_empty_canvas (WidgetAllocation allocation, Context cr) { Text t; - cr.save (); - t = new Text (t_("No glyphs in this view."), 24); + t = new Text ("No glyphs in this view.", 24); Theme.text_color (t, "Text Foreground"); t.widget_x = 40; t.widget_y = 30; @@ -1333,7 +1353,7 @@ } public override void button_press (uint button, double x, double y) { - OverViewItem i; + OverViewItem selected_item; int index = 0; int selected_index = -1; bool update = false; @@ -1349,15 +1369,14 @@ } for (int j = 0; j < visible_items.size; j++) { - i = visible_items.get (j); + selected_item = visible_items.get (j); - if (i.click (button, x, y)) { + if (selected_item.click (button, x, y)) { selected = index; selected_item = get_selected_item (); if (KeyBindings.has_shift ()) { if (selected_item.glyphs != null) { - selected_index = selected_items.index_of ((!) selected_item.glyphs); if (selected_index == -1) { selected_items.add ((!) selected_item.glyphs); @@ -1368,6 +1387,8 @@ selected_item = get_selected_item (); } } + + update = true; } else { selected_items.clear (); if (selected_item.glyphs != null) { @@ -1375,15 +1396,15 @@ } } - if (!is_null (i.version_menu)) { - update = !((!)i).version_menu.menu_visible; + if (!is_null (selected_item.version_menu)) { + update = !((!) selected_item.version_menu).menu_visible; } else { update = true; } } index++; } - + if (update) { update_item_list (); } @@ -1435,7 +1456,6 @@ selected_item = get_selected_item (); if (selected_item.glyphs != null) { open_glyph_signal ((!) selected_item.glyphs); - GlyphCollection? gc2 = selected_item.glyphs; GlyphCollection gc = (!) selected_item.glyphs; gc.get_current ().close_path (); } else {
--- a/libbirdfont/OverViewItem.vala +++ b/libbirdfont/OverViewItem.vala @@ -25,7 +25,11 @@ public GlyphCollection? glyphs; public double x; public double y; - public bool selected = false; + + public bool selected { + get; set; + } + public CharacterInfo info; public static double DEFAULT_WIDTH = 100; @@ -38,7 +42,7 @@ public static double glyph_scale = 1.0; - public VersionList version_menu; + public VersionList? version_menu = null; Text label; private Surface? cache = null; @@ -56,20 +60,25 @@ } public void set_glyphs (GlyphCollection? gc) { - glyphs = gc; - - if (glyphs != null) { - version_menu = new VersionList ((!) glyphs); - version_menu.add_glyph_item.connect ((glyph) => { + glyphs = gc; + } + + public void generate_graphics (bool generate_versions = true) { + if (glyphs != null && generate_versions) { + VersionList versions = new VersionList ((!) glyphs); + + versions.add_glyph_item.connect ((glyph) => { ((!) glyphs).insert_glyph (glyph, true); }); - version_menu.signal_delete_item.connect ((glyph_index) => { + versions.signal_delete_item.connect ((glyph_index) => { OverView v = MainWindow.get_overview (); version_menu = new VersionList ((!) glyphs); v.update_item_list (); GlyphCanvas.redraw (); }); + + version_menu = versions; } info = new CharacterInfo (character, glyphs); @@ -81,7 +90,7 @@ truncate_label (); } - draw_background (); + draw_background (); } public void clear_cache () { @@ -99,7 +108,6 @@ } Glyph g; - Font font; double gx, gy; double x1, x2, y1, y2; double scale_box; @@ -107,7 +115,6 @@ double glyph_width, glyph_height; Surface s; Context c; - Color color = Color.black (); g = ((!) glyphs).get_current (); @@ -115,10 +122,10 @@ cache = g.overview_thumbnail; return; } - + w = width; h = height; - + scale_box = width / DEFAULT_WIDTH; s = Screen.create_background_surface ((int) width, (int) height - 20); @@ -126,21 +133,20 @@ c.save (); g.boundaries (out x1, out y1, out x2, out y2); - + glyph_width = x2 - x1; glyph_height = y2 - y1; - + c.save (); c.scale (glyph_scale * Screen.get_scale (), glyph_scale * Screen.get_scale ()); g.add_help_lines (); - + gx = ((w / glyph_scale) - glyph_width) / 2 - g.get_left_side_bearing (); gy = (h / glyph_scale) - 25 / glyph_scale; - c.translate (gx - Glyph.xc () - g.get_lsb (), g.get_baseline () + gy - Glyph.yc ()); + g.draw_layers (c); - g.draw_paths (c, color); c.restore (); cache = s; @@ -150,27 +156,17 @@ } public void draw_background () { - Glyph g; - Font font; - double gx, gy; - double x1, x2, y1, y2; double scale_box; - double w, h; - double glyph_width, glyph_height; Surface s; Context c; - Color color = Color.black (); - - w = width; - h = height; - + scale_box = width / DEFAULT_WIDTH; adjust_scale (); s = Screen.create_background_surface ((int) width, (int) height - 20); c = new Context (s); - if (glyphs != null) { // FIXME: lock + if (glyphs != null) { draw_glyph_from_font (); } else { c.scale (Screen.get_scale (), Screen.get_scale ()); @@ -225,10 +221,6 @@ s.append_unichar (character); return s.str; - } - - public void set_selected (bool s) { - selected = s; } public static double full_width () { @@ -244,16 +236,17 @@ GlyphCollection g; bool s = (x <= px <= x + width) && (y <= py <= y + height); - if (has_icons () && glyphs != null) { + if (has_icons () && glyphs != null && version_menu != null) { g = (!) glyphs; - version_menu.set_position (x + width - 21, y + height - 18); - a = version_menu.menu_item_action (px, py); // select one item on the menu + VersionList versions = (!) version_menu; + versions.set_position (x + width - 21, y + height - 18); + a = versions.menu_item_action (px, py); // select one item on the menu if (a) { return s; } - version_menu.menu_icon_action (px, py); // click in the open menu + versions.menu_icon_action (px, py); // click in the open menu } info.set_position (x + width - 17, y + height - 22.5); @@ -292,7 +285,6 @@ cr.restore (); draw_thumbnail (cr, x, y + height); - draw_caption (cr); draw_menu (cr); } @@ -305,21 +297,22 @@ if (glyphs != null) { font = BirdFont.get_current_font (); g = ((!) glyphs).get_current (); - g.boundaries (out x1, out y1, out x2, out y2); - - glyph_width = x2 - x1; - glyph_height = y2 - y1; + + if (g.boundaries (out x1, out y1, out x2, out y2)) { + glyph_width = x2 - x1; + glyph_height = y2 - y1; - if (glyph_scale == 1) { - // caption height is 20 - glyph_scale = (height - 20) / (font.top_limit - font.bottom_limit); - } + if (glyph_scale == 1) { + // caption height is 20 + glyph_scale = (height - 20) / (font.top_limit - font.bottom_limit); + } + + scale = glyph_scale; + gx = ((width / scale) - glyph_width) / 2; - scale = glyph_scale; - gx = ((width / scale) - glyph_width) / 2; - - if (gx < 0) { - glyph_scale = 1 + 2 * gx / width; + if (gx < 0) { + glyph_scale = 1 + 2 * gx / width; + } } } } @@ -375,7 +368,10 @@ cc.fill (); if (has_icons ()) { - draw_menu_icon (cc, false); + if (version_menu != null) { + draw_menu_icon (cc, false); + } + draw_character_info_icon (cc); } @@ -468,7 +464,8 @@ public void hide_menu () { if (!is_null (version_menu)) { - version_menu.menu_visible = false; + VersionList v = (!) version_menu; + v.menu_visible = false; } } @@ -488,13 +485,23 @@ } private void draw_menu (Context cr) { - if (likely (glyphs == null || !version_menu.menu_visible)) { + if (glyphs == null) { + return; + } + + if (version_menu == null) { + return; + } + + VersionList v = (!) version_menu; + + if (!v.menu_visible) { return; } - version_menu.draw_menu (cr); + v.draw_menu (cr); } } }
--- a/libbirdfont/OverviewTools.vala +++ b/libbirdfont/OverviewTools.vala @@ -18,7 +18,8 @@ public enum Transform { SLANT, - SIZE + SIZE, + SVG_TO_TTF } public class OverviewTools : ToolCollection { @@ -188,6 +189,19 @@ Tool search_glyph = new Tool ("search", t_("Search")); search_glyph.select_action.connect (search_for_glyph); glyph_expander.add_tool (search_glyph); + + Tool convert_to_ttf = new Tool ("svg_to_birdfont_overview", t_("Convert SVG file to monochrome glyph")); + convert_to_ttf.set_icon ("svg_to_birdfont"); + + convert_to_ttf.select_action.connect ((self) => { + process_transform (Transform.SVG_TO_TTF); + self.set_selected (false); + BirdFont.get_current_font ().touch (); + }); + + convert_to_ttf.selected = false; + convert_to_ttf.set_persistent (false); + glyph_expander.add_tool (convert_to_ttf); SpinButton master_size; current_master_size = 0; @@ -291,51 +305,13 @@ MainWindow.tabs.add_tab (new OtfFeatureTable (gc)); } - - public void process_transform (Transform transform) { - OverView o; - Glyph g; - OverView.OverViewUndoItem ui; - - o = get_overview (); - ui = new OverView.OverViewUndoItem (); - - Font f = BirdFont.get_current_font (); - ui.alternate_sets = f.alternates.copy (); - - foreach (GlyphCollection gc in o.selected_items) { - if (gc.length () > 0) { - g = gc.get_current (); - ui.glyphs.add (gc.copy_deep ()); - g.add_help_lines (); - - if (transform == Transform.SLANT) { - if (skew.get_value () != 0) { - DrawingTools.resize_tool.skew_glyph (g, -skew.get_value (), 0, false); - } - } - - if (transform == Transform.SIZE) { - if (resize.get_value () != 100) { - double scale = resize.get_value () / 100; - DrawingTools.resize_tool.resize_glyph (g, scale, scale, false); - } - } - } - } - - foreach (OverViewItem item in o.visible_items) { - item.clear_cache (); - item.draw_glyph_from_font (); - } - - o.undo_items.add (ui); - - MainWindow.get_overview ().update_item_list (); - GlyphCanvas.redraw (); + + public void process_transform (Transform transform) { + TransformTask task = new TransformTask (transform); + MainWindow.run_blocking_task (task); } - public OverView get_overview () { + public static OverView get_overview () { FontDisplay fd = MainWindow.get_current_display (); if (fd is OverView || fd is GlyphSelection) { @@ -355,9 +331,16 @@ GlyphRange gr; uint size; OverView overview; - - // All characters - size = BirdFont.get_current_font ().length (); + Font font; + + // All characters including .notdef + font = BirdFont.get_current_font (); + size = font.length (); + + if (!font.has_glyph (".notdef")) { + size++; + } + all_glyphs.number = get_display_value (size); // Default
--- a/libbirdfont/Path.vala +++ b/libbirdfont/Path.vala @@ -14,6 +14,7 @@ using Cairo; using Math; + using SvgBird; namespace BirdFont { @@ -67,7 +68,7 @@ private double path_stroke_width = 0; - public LineCap line_cap = LineCap.BUTT; + public SvgBird.LineCap line_cap = SvgBird.LineCap.BUTT; public PathList? full_stroke = null; PathList? fast_stroke = null; StrokeTask? stroke_creator; @@ -83,14 +84,8 @@ bool clockwise_direction = true; // Iterate over each pixel in a path - public delegate bool RasterIterator (double x, double y, double step); - + public delegate bool RasterIterator (double x, double y, double step); public delegate bool SegmentIterator (EditPoint start, EditPoint stop); - - /** The stroke of an outline when the path is not filled. */ - public static double stroke_width = 0; - public static bool show_all_line_handles = true; - public static bool fill_open_path {get; set;} public double rotation = 0; public double skew = 0; @@ -99,27 +94,14 @@ public bool highlight_last_segment = false; public string point_data = ""; + + private static Text? arrow = null; public Color? color = null; public Color? stroke_color = null; - - public Gradient? gradient = null; - - private static Text? arrow = null; + public LinearGradient? gradient = null; public Path () { - string width; - - if (unlikely (stroke_width < 1)) { - width = Preferences.get ("stroke_width"); - if (width != "") { - stroke_width = double.parse (width); - } - } - - if (stroke_width < 1) { - stroke_width = 1; - } } public bool is_filled () { @@ -173,22 +155,6 @@ public bool empty () { return points.size == 0; - } - - public void draw_boundaries (Context cr) { - double x = Glyph.reverse_path_coordinate_x (xmin); - double y = Glyph.reverse_path_coordinate_y (ymin); - double x2 = Glyph.reverse_path_coordinate_x (xmax); - double y2 = Glyph.reverse_path_coordinate_y (ymax); - - cr.save (); - - Theme.color (cr, "Default Background"); - cr.set_line_width (2); - cr.rectangle (x, y, x2 - x, y2 - y); - cr.stroke (); - - cr.restore (); } public void draw_outline (Context cr) { @@ -217,7 +183,7 @@ i++; } - // close path + // closed path if (!is_open () && n != null) { if (highlight_last_segment) { cr.stroke (); @@ -242,19 +208,20 @@ } } - public void draw_edit_points (Context cr) { - if (is_editable ()) { - // control points for curvature - foreach (EditPoint e in points) { - if (show_all_line_handles || e.selected_point || e.selected_handle > 0) { - draw_edit_point_handles (e, cr); - } - } - - // control points - foreach (EditPoint e in points) { - draw_edit_point (e, cr); + public void draw_control_points (Context cr) { + // control points for curvature + foreach (EditPoint e in points) { + if (CanvasSettings.show_all_line_handles + || e.selected_point + || e.selected_handle > 0) { + + draw_edit_point_handles (e, cr); } + } + + // on curve control points + foreach (EditPoint e in points) { + draw_edit_point (e, cr); } } @@ -262,7 +229,7 @@ * Call Context.new_path (); before this method and Context.fill () * to show the path. */ - public void draw_path (Context cr, Glyph glyph, Color? color = null) { + public void draw_path (Context cr, Color? color = null) { unowned EditPoint? n = null; unowned EditPoint en; unowned EditPoint em; @@ -274,8 +241,8 @@ return; } - center_x = glyph.allocation.width / 2.0; - center_y = glyph.allocation.height / 2.0; + center_x = Glyph.xc (); + center_y = Glyph.yc (); ex = center_x + points.get (0).x; ey = center_y - points.get (0).y; @@ -300,7 +267,9 @@ } // fill path - cr.close_path (); + if (!is_open ()) { + cr.close_path (); + } if (this.color != null) { c = (!) this.color; @@ -308,12 +277,6 @@ } else if (color != null) { c = (!) color; cr.set_source_rgba (c.r, c.g, c.b, c.a); - } else { - if (is_clockwise ()) { - Theme.color_opacity (cr, "Selected Objects", 0.4); - } else { - Theme.color_opacity (cr, "Selected Objects", 0.8); - } } } @@ -356,7 +319,6 @@ cr.translate (-x * zoom, -y * zoom); arrow_icon.draw_at_baseline (cr, x * zoom, y * zoom); - cr.restore (); } } @@ -390,7 +352,6 @@ } private static void draw_curve (EditPoint e, EditPoint en, Context cr, bool highlighted = false, double alpha = 1) { - Glyph g = MainWindow.get_current_glyph (); double xa, ya, xb, yb, xc, yc, xd, yd; PointType t = e.get_right_handle ().type; PointType u = en.get_left_handle ().type; @@ -402,8 +363,6 @@ } else { Theme.color (cr, "Highlighted Guide"); } - - cr.set_line_width (stroke_width / g.view_zoom); cr.line_to (xa, ya); // this point makes sense only if it is in the first or last position @@ -470,7 +429,7 @@ get_line_points (e, en, out ax, out ay, out bx, out by); Theme.color (cr, "Handle Color"); - cr.set_line_width (1.7 * (stroke_width / g.view_zoom)); + cr.set_line_width (1.7 * (CanvasSettings.stroke_width / g.view_zoom)); cr.line_to (ax, ay); cr.line_to (bx, by); @@ -600,7 +559,7 @@ public static void draw_control_point (Context cr, double x, double y, Color color, double size = 3.5) { Glyph g = MainWindow.get_current_glyph (); double ivz = 1 / g.view_zoom; - double width = size * Math.sqrt (stroke_width) * ivz; + double width = size * Math.sqrt (CanvasSettings.stroke_width) * ivz; double xc = g.allocation.width / 2.0; double yc = g.allocation.height / 2.0; @@ -798,7 +757,7 @@ } if (gradient != null) { - new_path.gradient = ((!) gradient).copy (); + new_path.gradient = (LinearGradient)((!) gradient).copy (); } if (color != null) { @@ -823,21 +782,6 @@ new_path.highlight_last_segment = highlight_last_segment; return new_path; - } - - public bool is_over (double x, double y) { - Glyph g = MainWindow.get_current_glyph (); - - x = x * Glyph.ivz () + g.view_offset_x - Glyph.xc (); - y = y * Glyph.ivz () + g.view_offset_y - Glyph.yc (); - - y *= -1; - - return is_over_coordinate (x, y); - } - - public bool is_over_coordinate (double x, double y) { - return is_over_coordinate_var (x, y); } public static double point_distance (EditPoint p1, EditPoint p2) { @@ -894,7 +838,7 @@ } /** Variable precision */ - public bool is_over_coordinate_var (double x, double y) { + public bool is_over_coordinate (double x, double y) { int insides = 0; Path path; @@ -922,11 +866,6 @@ } public bool is_over_boundry (double x, double y) { - if (unlikely (ymin == double.MAX || ymin == 10000)) { - warning ("bounding box is not calculated, run update_region_boundaries first."); - update_region_boundaries (); - } - return (ymin <= y <= ymax) && (xmin <= x <= xmax); } @@ -995,6 +934,7 @@ points.add (p); p.prev = previous_point; p.next = previous_point.next; + previous_point.next = p; } last_point = p; @@ -1058,7 +998,7 @@ } if (gradient != null) { - Gradient g = (!) gradient; + LinearGradient g = (!) gradient; g.x1 += delta_x; g.x2 += delta_x; g.y1 += delta_y; @@ -2266,12 +2206,6 @@ PathList lines = new PathList (); lines = pl; - - /** // FIXME: Check automatic orientation. - foreach (Path p in pl.paths) { - lines.add (SvgParser.get_lines (p)); - } - */ foreach (Path p in lines.paths) { if (p.points.size > 1 && p != path @@ -2328,7 +2262,24 @@ } remove_deleted_points (); - + + // remove tripple points + for (int i = 0; i < points.size + 1 && points.size > 0; i++) { + EditPoint ep = points.get (i % points.size); + EditPoint next = points.get ((i + 1) % points.size); + EditPoint previous = points.get ((i - 1 + points.size) % points.size); + + if (Path.distance_to_point (next, ep) < t && Path.distance_to_point (previous, ep) < t) { + points.remove (ep); + i--; + } + } + + if (points.size == 0) { + return; + } + + // remove points on points for (int i = 0; i < points.size + 1; i++) { EditPoint ep = points.get (i % points.size); n = points.get ((i + 1) % points.size); @@ -2826,8 +2777,46 @@ next.get_left_handle ().move_to_coordinate ( ep.get_right_handle ().x, ep.get_right_handle ().y); } + } + + public void transform (Matrix matrix) { + double x, y; + EditPointHandle handle; + + foreach (EditPoint point in points) { + x = point.x; + y = point.y; + + matrix.transform_point (ref x, ref y); + + point.independent_x = x; + point.independent_y = y; + + handle = point.get_right_handle (); + + x = handle.x; + y = handle.y; + + matrix.transform_point (ref x, ref y); + + handle.independent_x = x; + handle.independent_y = y; + + handle = point.get_left_handle (); + + x = handle.x; + y = handle.y; + + matrix.transform_point (ref x, ref y); + + handle.independent_x = x; + handle.independent_y = y; + + } + + update_region_boundaries (); } } }
--- a/libbirdfont/PathList.vala +++ b/libbirdfont/PathList.vala @@ -11,6 +11,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ + + using SvgBird; namespace BirdFont { @@ -77,8 +79,22 @@ } return pl; + } + + public void apply_style (SvgStyle style) { + foreach (Path p in paths) { + if (style.has_stroke ()) { + p.stroke = style.get_stroke_width (); + } else { + p.stroke = 0; + } + + p.line_cap = style.get_line_cap (); + p.reset_stroke (); + p.update_region_boundaries (); + } } } }
diff --git libbirdfont/PathObject.vala(new)
--- /dev/null +++ b/libbirdfont/PathObject.vala @@ -1,1 +1,151 @@ + /* + Copyright (C) 2015 2016 Johan Mattsson + + 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 3 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. + */ + + using Cairo; + using SvgBird; + + namespace BirdFont { + + public class PathObject : SvgBird.Object { + + public Path path; + + public override double left { + get { + return path.xmin; + } + + set { + } + } + + public override double right { + get { + return path.xmax; + } + + set { + } + } + + public override double top { + get { + return -path.ymax; + } + + set { + } + } + + public override double bottom { + get { + return -path.ymin; + } + + set { + } + } + + public double stroke { + get { + return path.stroke; + } + + set { + path.stroke = value; + } + } + + public PathObject () { + base (); + path = new Path (); + } + + public PathObject.create_copy (PathObject p) { + base.create_copy (p); + path = p.path.copy (); + } + + public PathObject.for_path (Path path) { + base (); + this.path = path; + } + + public override bool is_over (double x, double y) { + return path.is_over_coordinate (x, y); + } + + public override void draw_outline (Context cr) { + // drawing is handled in Glyph.draw_bird_font_paths + + if (path.stroke > 0) { + //draw_path_list (path.get_completed_stroke (), cr); + draw_path_list (path.get_stroke_fast (), cr); + } else { + path.draw_path (cr); + } + } + + public void draw_path (Context cr) { + cr.save (); + apply_transform (cr); + + if (path.stroke > 0) { + draw_path_list (path.get_completed_stroke (), cr); + } else { + path.draw_path (cr); + } + + cr.restore (); + } + + public static void draw_path_list (PathList pl, Context cr) { + foreach (Path p in pl.paths) { + p.draw_path (cr); + } + } + + public override void move (double dx, double dy) { + path.move (dx, dy); + path.reset_stroke (); + } + + public Path get_path () { + return path; + } + + public override bool update_boundaries (Context cr) { + if (path.points.size < 2) { + return false; + } + + return base.update_boundaries (cr); + } + + public override bool is_empty () { + return path.points.size == 0; + } + + public override SvgBird.Object copy () { + return new PathObject.create_copy (this); + } + + public override string to_string () { + return "PathObject"; + } + + } + + }
--- a/libbirdfont/PenTool.vala +++ b/libbirdfont/PenTool.vala @@ -765,8 +765,11 @@ p.path.reset_stroke (); } - foreach (Path path in g.active_paths) { - path.reset_stroke (); + foreach (Object path in g.active_paths) { + if (path is PathObject) { + PathObject p = (PathObject) path; + p.get_path ().reset_stroke (); + } } } @@ -1051,11 +1054,15 @@ // don't use set point to reflective to on open ends reflective = true; - foreach (Path path in MainWindow.get_current_glyph ().active_paths) { - if (path.is_open () && path.points.size > 0) { - if (selected_handle.parent == path.get_first_point () - || selected_handle.parent == path.get_last_point ()) { - reflective = false; + foreach (SvgBird.Object path in MainWindow.get_current_glyph ().active_paths) { + if (!path.is_empty () && path is PathObject) { + Path p = ((PathObject) path).get_path (); + + if (p.is_open ()) { + if (selected_handle.parent == p.get_first_point () + || selected_handle.parent == p.get_last_point ()) { + reflective = false; + } } } } @@ -1225,7 +1232,10 @@ if (active_edit_point != null) { glyph.clear_active_paths (); - glyph.add_active_path (null, active_path); + + PathObject path = new PathObject.for_path (active_path); + glyph.add_active_object (path); + DrawingTools.update_stroke_settings (); if (KeyBindings.modifier != SHIFT) { @@ -1404,8 +1414,10 @@ foreach (Path merge in paths) { if (merge.points.size > 0) { - if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) { - glyph.add_active_path (null, merge); + PathObject merged_path = new PathObject.for_path (merge); + + if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) { + glyph.add_active_object (merged_path); active_path = merge; merge.reopen (); glyph.open_path (); @@ -1413,7 +1425,7 @@ } if (is_close_to_point (merge.points.get (0), px, py)) { - glyph.add_active_path (null, merge); + glyph.add_active_object (merged_path); active_path = merge; clear_directions (); merge.reopen (); @@ -1443,14 +1455,15 @@ } // join path with it self - if (is_close_to_point (path.points.get (0), px, py)) { - + if (is_close_to_point (path.points.get (0), px, py) && path.points.size > 2) { close_path (path); glyph.close_path (); force_direction (); glyph.clear_active_paths (); - glyph.add_active_path (null, path); + + PathObject closed_path = new PathObject.for_path (path); + glyph.add_active_object (closed_path); if (direction_changed) { path.reverse (); @@ -1489,11 +1502,12 @@ } else { union = merge_open_paths (path, merge); + PathObject union_path = new PathObject.for_path (union); glyph.add_path (union); glyph.delete_path (path); glyph.delete_path (merge); glyph.clear_active_paths (); - glyph.add_active_path (null, union); + glyph.add_active_object (union_path); union.reopen (); union.create_list (); @@ -1774,13 +1788,19 @@ active_edit_point = new_point.point; return_val_if_fail (glyph.active_paths.size > 0, new PointSelection.empty ()); - add_selected_point (selected_point, glyph.active_paths.get (glyph.active_paths.size - 1)); + SvgBird.Object object = glyph.active_paths.get (glyph.active_paths.size - 1); + + if (object is PathObject) { + Path path = ((PathObject) object).get_path (); + + add_selected_point (selected_point, path); - active_path = new_point.path; - glyph.clear_active_paths (); - glyph.add_active_path (null, new_point.path); - - move_selected = true; + active_path = new_point.path; + glyph.clear_active_paths (); + glyph.add_active_object (object); + + move_selected = true; + } return new_point; } @@ -1816,14 +1836,16 @@ EditPoint inserted; bool stroke = StrokeTool.add_stroke; Glyph g = MainWindow.get_current_glyph (); + PathObject path; - if (g.active_paths.size == 0) { + if (!g.has_active_path_objects ()) { np = new Path (); g.add_path (np); np.stroke = stroke ? StrokeTool.stroke_width : 0; np.line_cap = StrokeTool.line_cap; - g.add_active_path (null, np); + path = new PathObject.for_path (np); + g.add_active_object (path); active_path = np; selected_path = np; @@ -1844,12 +1866,17 @@ if (DrawingTools.pen_tool.is_selected ()) { np.stroke = PenTool.path_stroke_width; } + + path = new PathObject.for_path (np); + g.add_active_object (path); PenTool.active_path = np; + PenTool.selected_path = np; } g.clear_active_paths (); - g.add_active_path (null, np); + path = new PathObject.for_path (np); + g.add_active_object (path); active_path = np; selected_path = np; @@ -1900,7 +1927,7 @@ Glyph g = MainWindow.get_current_glyph (); double distance_to_edit_point = g.view_zoom * get_distance_to_closest_edit_point (event_x, event_y); - if (!Path.show_all_line_handles) { + if (!CanvasSettings.show_all_line_handles) { foreach (PointSelection selected_corner in selected_points) { if (is_close_to_handle (selected_corner.point, event_x, event_y, distance_to_edit_point)) { return true; @@ -1970,7 +1997,7 @@ foreach (Path p in g.get_paths_in_current_layer ()) { foreach (EditPoint ep in p.points) { - if (ep.is_selected () || Path.show_all_line_handles) { + if (ep.is_selected () || CanvasSettings.show_all_line_handles) { left = ep.get_left_handle (); right = ep.get_right_handle (); @@ -2059,7 +2086,8 @@ selected_handle.selected = true; active_path = p.path; - g.add_active_path (null, active_path); + PathObject path = new PathObject.for_path (active_path); + g.add_active_object (path); } public static void add_selected_point (EditPoint p, Path path) {
--- a/libbirdfont/QuestionDialog.vala +++ b/libbirdfont/QuestionDialog.vala @@ -28,12 +28,12 @@ double height = 0; public QuestionDialog (string message) { - question = new TextArea (font_size); + Color color = Theme.get_color ("Text Tool Box"); + question = new TextArea (font_size, color); question.min_width = 300; question.min_height = font_size; question.set_editable (false); question.draw_border = false; - question.text_color = Theme.get_color ("Text Tool Box"); question.set_text (message); buttons = new Gee.ArrayList<Button> (); }
--- a/libbirdfont/Renderer/CachedFont.vala +++ /dev/null @@ -1,100 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - 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 3 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. - */ - - using Gee; - - namespace BirdFont { - - public class CachedFont : GLib.Object { - public Font? font; - - public double top_limit { - get { return _top_limit; } - set { _top_limit = value; } - } - - public double bottom_limit { - get { return _bottom_limit; } - set { _bottom_limit = value; } - } - - public double base_line = 0; - double _top_limit = 92.77; // FIXME: load before first glyph - double _bottom_limit = -24.4; - - static FallbackFont fallback_font { - get { - if (_fallback_font == null) { - _fallback_font = new FallbackFont (); - } - - return (!) _fallback_font; - } - } - static FallbackFont? _fallback_font = null; - - public CachedFont (Font? font) { - Glyph? g; - Glyph glyph; - - this.font = font; - - g = get_glyph_by_name ("a"); - if (g != null) { - glyph = (!) g; - base_line = glyph.baseline; - top_limit = glyph.top_limit; - bottom_limit = glyph.bottom_limit; - } else { - warning("No default chararacter found in font."); - } - } - - public Glyph? get_glyph_by_name (string name) { - Glyph? g = null; - Font f; - Glyph glyph; - - if (font != null) { - f = (!) font; - g = f.get_glyph_by_name (name); - - if (g != null) { - glyph = (!) g; - glyph.top_limit = f.top_limit; - glyph.baseline = f.base_line; - glyph.bottom_limit = f.bottom_limit; - } - } - - if (g == null && name.char_count () == 1) { - f = fallback_font.get_single_glyph_font (name.get_char (0)); - g = f.get_glyph_by_name (name); - - if (g == null) { - return null; - } - - glyph = (!) g; - glyph.top_limit = f.top_limit; - glyph.baseline = f.base_line; - glyph.bottom_limit = f.bottom_limit; - } - - return g; - } - } - - }
diff --git libbirdfont/Renderer/Drawing.vala(deleted)
--- a/libbirdfont/Renderer/Drawing.vala +++ /dev/null @@ -1,51 +1,1 @@ - /* - Copyright (C) 2014 Johan Mattsson - - 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 3 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. - */ - - namespace BirdFont { - - /** Interface for creating drawing callbacks. */ - [Compact] - [CCode (ref_function = "bird_font_drawing_ref", unref_function = "bird_font_drawing_unref")] - public class Drawing { - - public int iterator_refcount = 1; - - public Drawing () { - } - - public void new_path (double x, double y) { - } - - public void curve_to (double xb, double yb, double xc, double yc, double xd, double yd) { - } - - public void close_path (double x, double y) { - } - - public unowned Drawing @ref () { - iterator_refcount++; - return this; - } - - public void unref () { - if (--iterator_refcount == 0) { - this.free (); - } - } - - private extern void free (); - } - - }
--- a/libbirdfont/Renderer/FallbackFont.vala +++ /dev/null @@ -1,337 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - 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 3 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. - */ - - using Gee; - - [SimpleType] - [CCode (has_type_id = false)] - public extern struct FcConfig { - } - - [CCode (cname = "FcInitLoadConfigAndFonts")] - public extern FcConfig* FcInitLoadConfigAndFonts (); - - [CCode (cname = "FcConfigAppFontAddDir")] - public extern string* FcConfigAppFontAddDir (FcConfig* config, string path); - - [CCode (cname = "FcConfigSetSysRoot")] - public extern void FcConfigSetSysRoot (FcConfig* config, string path); - - [CCode (cname = "FcConfigParseAndLoad")] - public extern bool FcConfigParseAndLoad (FcConfig* config, string path, bool complain); - - [CCode (cname = "FcConfigSetCurrent")] - public extern void FcConfigSetCurrent (FcConfig* config); - - [CCode (cname = "FcConfigCreate")] - public extern FcConfig* FcConfigCreate (); - - [CCode (cname = "FcConfigFilename")] - public extern string FcConfigFilename (string path); - - [CCode (cname = "find_font")] - public extern string? find_font (FcConfig* font_config, string characters); - - [CCode (cname = "find_font_family")] - public extern string? find_font_family (FcConfig* font_config, string characters); - - [CCode (cname = "find_font_file")] - public extern string? find_font_file (FcConfig* font_config, string font_name); - - namespace BirdFont { - - // TODO: use font config - public class FallbackFont : GLib.Object { - Gee.ArrayList<File> font_directories; - - FontFace* default_font = null; - public static FcConfig* font_config = null; - static bool font_config_started = false; - - string default_font_file_name = "Roboto-Regular.ttf"; - string default_font_family_name = "Roboto"; - - Gee.HashMap<unichar, CachePair> glyphs; - Gee.ArrayList<CachePair> cached; - - public int max_cached_fonts = 300; - - string? default_font_file = null; - - public FallbackFont () { - string home = Environment.get_home_dir (); - font_directories = new Gee.ArrayList<File> (); - - if (!font_config_started) { - font_config_started = true; - - IdleSource idle = new IdleSource (); - idle.set_callback (() => { - Task t = new Task (init_font_config); - MainWindow.native_window.run_non_blocking_background_thread (t); - return false; - }); - idle.attach (null); - } - - add_font_folder ("/usr/share/fonts/"); - add_font_folder ("/usr/local/share/fonts/"); - add_font_folder (home + "/.local/share/fonts"); - add_font_folder (home + "/.fonts"); - add_font_folder ("C:\\Windows\\Fonts"); - add_font_folder (home + "/Library/Fonts"); - add_font_folder ("/Library/Fonts"); - add_font_folder ("/Network/Library/Fonts"); - add_font_folder ("/System/Library/Fonts"); - add_font_folder ("/System Folder/Fonts"); - - glyphs = new Gee.HashMap<unichar, CachePair> (); - cached = new Gee.ArrayList<CachePair> (); - - open_default_font (); - } - - ~FallbackFont () { - if (default_font != null) { - close_font (default_font); - } - } - - public void init_font_config () { - FcConfig* config; - - #if MAC - config = FcConfigCreate(); - - string bundle = (!) BirdFont.get_settings_directory ().get_path (); - FcConfigSetSysRoot(config, bundle); - - string path = FcConfigFilename((!) SearchPaths.search_file(null, "fontconfig.settings").get_path ()); - bool loaded = FcConfigParseAndLoad(config, path, true); - - if (!loaded) { - warning ("Cannot load fontconfig."); - } - - FcConfigSetCurrent (config); - #else - config = FcInitLoadConfigAndFonts (); - #endif - - IdleSource idle = new IdleSource (); - - idle.set_callback (() => { - font_config = config; - return false; - }); - idle.attach (null); - } - - public Font get_single_glyph_font (unichar c) { - Font f; - unichar last; - CachePair p; - - if (likely (glyphs.has_key (c))) { - p = glyphs.get (c); - - if (p.referenced < int.MAX) { - p.referenced++; - } - - return p.font; - } - - // remove glyphs from cache if it is full - if (cached.size > max_cached_fonts - 100) { - - cached.sort ((a, b) => { - CachePair pa = (CachePair) a; - CachePair pb = (CachePair) b; - return pb.referenced - pa.referenced; - }); - - int j = 0; - for (int i = cached.size - 1; i > 0; i--) { - if (j > 100) { - break; - } - - j++; - - last = cached.get (i).character; - glyphs.unset (last); - cached.remove_at (i); - } - } - - f = get_single_fallback_glyph_font (c); - p = new CachePair (f, c); - - glyphs.set (c, p); - cached.add (p); - - return (Font) f; - } - - Font get_single_fallback_glyph_font (unichar c) { - string? font_file; - BirdFontFile bf_parser; - Font bf_font; - StringBuilder? glyph_data; - FontFace* font; - - bf_font = new Font (); - font_file = null; - glyph_data = null; - - // don't use fallback font in private use area - if (0xe000 <= c <= 0xf8ff) { - return bf_font; - } - - // control characters - if (c <= 0x001f || (0x007f <= c <= 0x008d)) { - return bf_font; - } - - // check if glyph is available in roboto - if (default_font != null) { - glyph_data = get_glyph_in_font ((!) default_font, c); - } - - // use fontconfig to find a fallback font - if (glyph_data == null) { - font_file = find_font (font_config, (!) c.to_string ()); - if (font_file != null) { - font = open_font ((!) font_file); - glyph_data = get_glyph_in_font (font, c); - close_font (font); - } - } - - if (glyph_data != null) { - bf_parser = new BirdFontFile (bf_font); - bf_parser.load_data (((!) glyph_data).str); - } - - return bf_font; - } - - public StringBuilder? get_glyph_in_font (FontFace* font, unichar c) { - StringBuilder? glyph_data = null; - GlyphCollection gc; - - gc = new GlyphCollection (c, (!)c.to_string ()); - glyph_data = load_glyph (font, (uint) c); - - return glyph_data; - } - - void add_font_folder (string f) { - File folder = File.new_for_path (f); - FileInfo? file_info; - string fn; - string file_attributes; - try { - if (folder.query_exists ()) { - font_directories.add (folder); - - file_attributes = FileAttribute.STANDARD_NAME; - file_attributes += ","; - file_attributes += FileAttribute.STANDARD_TYPE; - var enumerator = folder.enumerate_children (file_attributes, 0); - - while ((file_info = enumerator.next_file ()) != null) { - fn = ((!) file_info).get_name (); - - if (((!)file_info).get_file_type () == FileType.DIRECTORY) { - add_font_folder ((!) get_child (folder, fn).get_path ()); - } - } - } - } catch (GLib.Error e) { - warning (e.message); - } - } - - File search_font_file (string font_file) { - File d, f; - - for (int i = font_directories.size - 1; i >= 0; i--) { - d = font_directories.get (i); - f = get_child (d, font_file); - - if (f.query_exists ()) { - return f; - } - } - - warning (@"The font $font_file not found"); - return File.new_for_path (font_file); - } - - public string? get_default_font_file () { - File font_file; - string? fn = null; - - if (likely (default_font_file != null)) { - return default_font_file; - } - - font_file = SearchPaths.search_file (null, default_font_file_name); - - if (font_file.query_exists ()) { - fn = (!) font_file.get_path (); - } else { - font_file = search_font_file (default_font_file_name); - - if (font_file.query_exists ()) { - fn = (!) font_file.get_path (); - } else { - fn = find_font_file (font_config, default_font_family_name); - } - } - - if (likely (fn != null)) { - default_font_file = fn; - return fn; - } - - warning(default_font_family_name + " not found"); - return null; - } - - void open_default_font () { - string? fn = get_default_font_file (); - - if (fn != null) { - default_font = open_font ((!) fn); - } - } - - class CachePair : GLib.Object { - public Font font; - public unichar character; - public int referenced = 1; - - public CachePair (Font f, unichar c) { - font = f; - character = c; - } - } - } - - }
--- a/libbirdfont/Renderer/FontCache.vala +++ /dev/null @@ -1,84 +1,1 @@ - /* - Copyright (C) 2014 Johan Mattsson - - 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 3 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. - */ - - using Gee; - - namespace BirdFont { - - /** Thread specific font cache. */ - public class FontCache { - public static FallbackFont fallback_font; - - static FontCache? default_cache = null; - Gee.HashMap<string, CachedFont> fonts; - CachedFont fallback; - - public FontCache () { - if (is_null (fallback_font)) { - fallback_font = new FallbackFont (); - } - - fallback = new CachedFont (null); - fonts = new Gee.HashMap<string, CachedFont> (); - } - - public CachedFont get_font (string file_name) { - CachedFont c; - Font f; - bool ok; - - if (file_name == "") { - return fallback; - } - - if (fonts.has_key (file_name)) { - c = fonts.get (file_name); - return c; - } - - f = new Font (); - f.set_file (file_name); - ok = f.load (); - if (!ok) { - stderr.printf ("Can't load %s\n", file_name); - return new CachedFont (null); - } - - c = new CachedFont (f); - - if (file_name == "") { - warning ("No file."); - return c; - } - - fonts.set (file_name, c); - return c; - } - - public static FontCache get_default_cache () { - if (default_cache == null) { - default_cache = new FontCache (); - } - - return (!) default_cache; - } - - public CachedFont get_fallback () { - return fallback; - } - - } - - }
--- a/libbirdfont/Renderer/LineTextArea.vala +++ /dev/null @@ -1,34 +1,1 @@ - /* - Copyright (C) 2014 Johan Mattsson - - 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 3 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. - */ - - using Cairo; - using Math; - - namespace BirdFont { - - public class LineTextArea : TextArea { - - public LineTextArea (double size) { - base (size); - - single_line = true; - min_height = size; - height = min_height; - - layout (); - } - } - - }
diff --git libbirdfont/Renderer/Text.vala(deleted)
--- a/libbirdfont/Renderer/Text.vala +++ /dev/null @@ -1,592 +1,1 @@ - /* - Copyright (C) 2014 2015 Johan Mattsson - - 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 3 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. - */ - - using Cairo; - using Math; - - namespace BirdFont { - - /** Test implementation of a birdfont rendering engine. */ - public class Text : Widget { - FontCache font_cache; - public CachedFont cached_font; - - Surface? cache = null; - - public string text; - - private bool use_cache = true; - - GlyphSequence glyph_sequence { - get { - if (gs == null) { - gs = generate_glyphs (); - } - - return (!) gs; - } - } - - Gee.ArrayList<string> glyph_names; - GlyphSequence? gs = null; - - public delegate void Iterator (Glyph glyph, double kerning, bool last); - public double font_size; - double sidebearing_extent = 0; - - public double r = 0; - public double g = 0; - public double b = 0; - public double a = 1; - double truncated_width = -1; - - double margin_left = 0; - - public Text (string text = "", double size = 17, double margin_bottom = 0) { - this.margin_bottom = margin_bottom; - font_cache = FontCache.get_default_cache (); - cached_font = font_cache.get_fallback (); - - set_text (text); - set_font_size (size); - } - - public void set_use_cache (bool cache) { - use_cache = cache; - } - - public string get_text () { - return text; - } - - /** Set font for this text area. - * @param font_absolute path to the font file or a file name for one of the font files in search paths. - * @return true if the font was found - */ - public bool load_font (string font_file) { - File path; - File f; - FontCache fc; - - f = File.new_for_path (font_file); - path = (f.query_exists ()) ? f : SearchPaths.find_file (null, font_file); - - fc = FontCache.get_default_cache (); - cached_font = fc.get_font ((!) path.get_path ()); - gs = generate_glyphs (); - - return cached_font.font != null; - } - - public void set_font_size (double height_in_pixels) { - font_size = height_in_pixels; - sidebearing_extent = 0; - - if (gs == null) { // ensure height is loaded for the font - gs = generate_glyphs (); - } - } - - public void set_font_cache (FontCache font_cache) { - this.font_cache = font_cache; - } - - public void set_text (string text) { - this.text = text; - gs = null; - sidebearing_extent = 0; - cache = null; - } - - private GlyphSequence generate_glyphs () { - int index; - unichar c; - string name; - Glyph? g; - GlyphSequence gs; - - gs = new GlyphSequence (); - - glyph_names = new Gee.ArrayList<string> (); - index = 0; - while (text.get_next_char (ref index, out c)) { - name = (!) c.to_string (); - g = cached_font.get_glyph_by_name (name); - - gs.glyph.add (g); - glyph_names.add (name); - } - - return gs; - } - - public void iterate (Iterator iter) { - Glyph glyph; - double w, kern; - int wi; - Glyph? prev; - Glyph? g; - GlyphSequence word_with_ligatures; - GlyphRange? gr_left, gr_right; - GlyphSequence word; - KerningClasses kc; - Font empty = Font.empty; - - glyph = new Glyph.no_lines ("", '\0'); - - w = 0; - prev = null; - kern = 0; - - word = glyph_sequence; - wi = 0; - - if (cached_font.font != null) { - word_with_ligatures = word.process_ligatures ((!) cached_font.font); - } else { - word_with_ligatures = word.process_ligatures (new Font ()); - } - - gr_left = null; - gr_right = null; - - if (cached_font.font != null) { - kc = ((!) cached_font.font).get_kerning_classes (); - } else { - kc = new KerningClasses (empty); - } - - if (word_with_ligatures.glyph.size > 0) { - double none = 0; - g = word_with_ligatures.glyph.get (0); - if (g != null) { - margin_left = ((!) g).get_left_side_bearing (); - - if (margin_left < 0) { - margin_left = -margin_left; - } else { - margin_left = 0; - } - } - } - - for (int i = 0; i < word_with_ligatures.glyph.size; i++) { - g = word_with_ligatures.glyph.get (i); - - if (g == null || prev == null || wi == 0) { - kern = 0; - } else { - return_if_fail (wi < word_with_ligatures.ranges.size); - return_if_fail (wi - 1 >= 0); - - gr_left = word_with_ligatures.ranges.get (wi - 1); - gr_right = word_with_ligatures.ranges.get (wi); - - kern = kc.get_kerning_for_pair (((!) prev).get_name (), ((!) g).get_name (), gr_left, gr_right); - } - - // process glyph - if (g == null && (0 <= i < glyph_names.size)) { - g = cached_font.get_glyph_by_name (glyph_names.get (i)); - } - - glyph = (g == null) ? new Glyph ("") : (!) g; - iter (glyph, kern, i + 1 == word_with_ligatures.glyph.size); - prev = g; - wi++; - } - } - - // FIXME: some fonts doesn't have on curve extrema - public double get_extent () { - double x = 0; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double lsb; - - lsb = glyph.left_limit; - - if (!last) { - x += (glyph.get_width () + kerning) * get_scale (glyph); - } else { - glyph.boundaries (out x1, out y1, out x2, out y2); - x += (x2 - lsb) * get_scale (glyph); - } - }); - - return x; - } - - public double get_sidebearing_extent () { - double x ; - - if (likely (sidebearing_extent > 0)) { - return sidebearing_extent; - } - - x = 0; - - iterate ((glyph, kerning, last) => { - double lsb; - lsb = glyph.left_limit; - x += (glyph.get_width () + kerning) * get_scale (glyph); - }); - - sidebearing_extent = x; - return x; - } - - public override double get_height () { - return font_size; - } - - public double get_acender () { - double max_height = 0; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double h; - glyph.boundaries (out x1, out y1, out x2, out y2); - h = Math.fmax (y1, y2) - Math.fmin (y1, y2); - h *= get_scale (glyph) - glyph.baseline * get_scale (glyph); - if (h > max_height) { - max_height = h; - } - }); - - return max_height; - } - - public override double get_width () { - double x = 0; - bool first = true; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double lsb; - - lsb = glyph.left_limit; - - if (first) { - glyph.boundaries (out x1, out y1, out x2, out y2); - x += (glyph.get_width () + kerning - Math.fmin (x1, x2)) * get_scale (glyph); - first = false; - } else if (!last) { - x += (glyph.get_width () + kerning) * get_scale (glyph); - } else { - glyph.boundaries (out x1, out y1, out x2, out y2); - x += (x2 - lsb) * get_scale (glyph); - } - }); - - return x; - } - - public double get_decender () { - double decender_max = get_max_decender (); - return decender_max > 0 ? decender_max : 0; - } - - private double get_max_decender () { - double decender = 0; - double decender_max = 0; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double y; - glyph.boundaries (out x1, out y1, out x2, out y2); - y = Math.fmin (y1, y2); - decender = (glyph.baseline - y) * get_scale (glyph); - if (decender > decender_max) { - decender_max = decender; - } - }); - - return decender_max; - } - - public override void draw (Context cr) { - double descender = cached_font.bottom_limit + cached_font.base_line; - double y = widget_y + get_height () + get_font_scale () * descender; // FIXME: - draw_at_baseline (cr, widget_x, y); - } - - public void draw_at_top (Context cr, double px, double py, string cacheid = "") { - double s = get_font_scale (); - double y = py + s * (cached_font.top_limit - cached_font.base_line); - draw_at_baseline (cr, px, y, cacheid); - } - - public void set_source_rgba (double r, double g, double b, double a) { - if (this.r != r || - this.g != g || - this.b != b || - this.a != a) { - - this.r = r; - this.g = g; - this.b = b; - this.a = a; - cache = null; - } - } - - public string get_cache_id (int offset_x, int offset_y) { - string key; - int64 c; - - c = (((int64) (r * 255)) << 24) - | (((int64) (g * 255)) << 16) - | (((int64) (b * 255)) << 8) - | (((int64) (a * 255)) << 0); - - // FIXME: use binary key - key = @"$font_size $c $offset_x $offset_y"; - - return key; - } - - public void draw_at_baseline (Context cr, double px, double py, string cacheid = "") { - double x, y; - double ratio; - double cc_y; - - if (cache == null) { - cache = draw_on_cache_surface (cacheid); - } - - double screen_scale = Screen.get_scale (); - double font_scale = get_font_scale (); - double cache_y = py - font_scale * (cached_font.top_limit - cached_font.base_line); - - cr.save(); - cr.scale (1 / screen_scale, 1 / screen_scale); - double scaled_x = (px - margin_left) * screen_scale; - double scaled_y = cache_y * screen_scale; - cr.set_source_surface ((!) cache, (int) rint (scaled_x), (int) rint (scaled_y)); - cr.paint (); - cr.restore(); - } - - Surface draw_on_cache_surface (string cacheid) { - double y; - double ratio; - double cc_y; - Context cr; - Surface cache_surface; - double screen_scale = Screen.get_scale(); - double h = font_size * screen_scale + 1; - - ratio = get_font_scale (); - cc_y = (cached_font.top_limit - cached_font.base_line) * ratio; - - // double x = margin_left * ratio; - double x = 0; - double py = cc_y; - - double w = get_sidebearing_extent () * screen_scale + x + margin_left + 1; - - cache_surface = Screen.create_background_surface ((int) w, (int) h); - cr = new Context (cache_surface); - cr.scale (screen_scale, screen_scale); - - y = cc_y; - - if (unlikely (cached_font.base_line != 0)) { - warning ("Base line not zero."); - } - - iterate ((glyph, kerning, last) => { - double end; - - x += kerning * ratio; - end = x + glyph.get_width () * ratio; - - // truncation - if (truncated_width > 0 && end > truncated_width) { - return; - } - - if (use_cache) { - draw_chached (cr, glyph, kerning, last, x, y, cc_y, - ratio, cacheid); - } else { - draw_without_cache (cr, glyph, kerning, last, x, y, cc_y, ratio); - } - - x = end; - }); - - return cache_surface; - } - - void draw_without_cache (Context cr, Glyph glyph, double kerning, bool last, - double x, double y, double cc_y, double ratio) { - - double lsb; - - cr.save (); - cr.set_source_rgba (r, g, b, a); - cr.new_path (); - - lsb = glyph.left_limit; - - foreach (Path path in glyph.get_visible_paths ()) { - draw_path (cr, glyph, path, lsb, x, y, ratio); - } - - cr.fill (); - cr.restore (); - - } - - void draw_chached (Context cr, Glyph glyph, double kerning, bool last, - double x, double y, double cc_y, double ratio, - string cacheid = "") { - - double lsb; - Surface cache; - Surface cached_glyph; - Context cc; - string cache_id; - double glyph_margin_left = glyph.get_left_side_bearing (); - - if (glyph_margin_left < 0) { - glyph_margin_left = -glyph_margin_left; - } else { - glyph_margin_left = 0; - } - - double xp = (x - glyph_margin_left * ratio) * Screen.get_scale (); - double yp = (y - cc_y) * Screen.get_scale (); - int offset_x, offset_y; - - offset_x = (int) (10 * (xp - (int) xp)); - offset_y = (int) (10 * (yp - (int) yp)); - - cache_id = (cacheid == "") ? get_cache_id (offset_x, offset_y) : cacheid; - - if (!glyph.has_cache (cache_id)) { - int w = (int) ((2 * glyph_margin_left * ratio + glyph.get_width ()) * ratio) + 2; - int h = (int) font_size + 2; - cache = Screen.create_background_surface (w, h); - cc = new Context (cache); - - cc.scale(Screen.get_scale (), Screen.get_scale ()); - - lsb = glyph.left_limit - glyph_margin_left; - - cc.save (); - cc.set_source_rgba (r, g, b, a); - cc.new_path (); - - foreach (Path path in glyph.get_visible_paths ()) { - draw_path (cc, glyph, path, lsb, glyph_margin_left * ratio + offset_x / 10.0, cc_y + offset_y / 10.0, ratio); - } - - cc.fill (); - cc.restore (); - - if (use_cache) { - glyph.set_cache (cache_id, cache); - } - - cached_glyph = cache; - } else { - cached_glyph = glyph.get_cache (cache_id); - } - - cr.save (); - cr.set_antialias (Cairo.Antialias.NONE); - cr.scale(1 / Screen.get_scale (), 1 / Screen.get_scale ()); - cr.set_source_surface (cached_glyph, - (int) xp, - (int) yp); - cr.paint (); - cr.restore (); - } - - void draw_path (Context cr, Glyph glyph, Path path, - double lsb, double x, double y, double scale) { - - EditPoint e, prev; - double xa, ya, xb, yb, xc, yc, xd, yd; - double by; - double s = get_scale (glyph); - - if (path.points.size > 0) { - if (unlikely (path.is_open ())) { - warning (@"Path is open in $(glyph.get_name ())."); - } - - //path.add_hidden_double_points (); // FIXME: this distorts shapes - - prev = path.points.get (path.points.size - 1); - xa = (prev.x - lsb) * s + x; - ya = y - prev.y * s; - cr.move_to (xa, ya); - - by = (y - cached_font.base_line * s); - for (int i = 0; i < path.points.size; i++) { - e = path.points.get (i).copy (); - - PenTool.convert_point_segment_type (prev, e, PointType.CUBIC); - - xb = (prev.get_right_handle ().x - lsb) * s + x; - yb = by - prev.get_right_handle ().y * s; - - xc = (e.get_left_handle ().x - lsb) * s + x; - yc = by - e.get_left_handle ().y * s; - - xd = (e.x - lsb) * s + x; - yd = by - e.y * s; - - cr.curve_to (xb, yb, xc, yc, xd, yd); - cr.line_to (xd, yd); - - prev = e; - } - } - } - - public double get_baseline_to_bottom (Glyph g) { - return get_scale (g) * (-g.baseline - g.bottom_limit); - } - - public double get_scale (Glyph g) { - double s = g.top_limit - g.bottom_limit; - - if (s == 0) { - s = cached_font.top_limit - cached_font.bottom_limit; - } - - return font_size / s; - } - - public double get_font_scale () { - return font_size / (cached_font.top_limit - cached_font.bottom_limit); - } - - public double get_baseline_to_bottom_for_font () { - return get_font_scale () * (-cached_font.base_line - cached_font.bottom_limit); - } - - public void truncate (double max_width) { - truncated_width = max_width; - } - } - - }
--- a/libbirdfont/Renderer/TextArea.vala +++ /dev/null @@ -1,1666 +1,1 @@ - /* - Copyright (C) 2014 Johan Mattsson - - 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 3 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. - */ - - using Cairo; - using Math; - - namespace BirdFont { - - public class TextArea : Widget { - - public double min_width = 500; - public double min_height = 100; - public double font_size; - public double padding = 3.3; - public bool single_line = false; - public Color text_color = Color.black (); - - public bool draw_carret { - get { return carret_is_visible; } - set { - carret_is_visible = value; - if (!value) { - update_selection = false; - selection_end = carret.copy (); - } - } - } - public bool carret_is_visible = false; - public bool draw_border = true; - - public double width; - public double height; - - Carret carret = new Carret (); - Carret selection_end = new Carret (); - bool update_selection = false; - public bool show_selection = false; - - public signal void scroll (double pixels); - public signal void text_changed (string text); - public signal void enter (string text); - - Gee.ArrayList<Paragraph> paragraphs = new Gee.ArrayList<Paragraph> (); - private static const int DONE = -2; - - int last_paragraph = 0; - string text; - int text_length; - - Gee.ArrayList<TextUndoItem> undo_items = new Gee.ArrayList<TextUndoItem> (); - Gee.ArrayList<TextUndoItem> redo_items = new Gee.ArrayList<TextUndoItem> (); - - bool store_undo_state_at_next_event = false; - - public bool editable; - - public TextArea (double font_size = 20, Color? c = null) { - this.font_size = font_size; - width = min_width; - height = min_height; - editable = true; - - if (c != null) { - text_color = (!) c; - } - } - - public override void focus (bool focus) { - draw_carret = focus; - } - - public override double get_height () { - return height + 2 * padding; - } - - public override double get_width () { - return width + 2 * padding; - } - - public void set_font_size (double z) { - font_size = z; - } - - bool generate_paragraphs () { - Paragraph paragraph; - - int next_paragraph = -1; - - if (is_null (text)) { - warning ("No text"); - return false; - } - - if (last_paragraph == DONE) { - return false; - } - - next_paragraph = text.index_of ("\n", last_paragraph); - - if (next_paragraph == -1) { - paragraph = new Paragraph (text.substring (last_paragraph), font_size, paragraphs.size, text_color); - paragraphs.add (paragraph); - last_paragraph = DONE; - } else { - next_paragraph += "\n".length; - paragraph = new Paragraph (text.substring (last_paragraph, next_paragraph - last_paragraph), font_size, paragraphs.size, text_color); - paragraphs.add (paragraph); - last_paragraph = next_paragraph; - } - - return last_paragraph != DONE; - } - - void generate_all_paragraphs () { - while (generate_paragraphs ()) { - } - } - - public override void key_press (uint keyval) { - unichar c; - TextUndoItem ui; - - if (!editable) { - return; - } - - c = (unichar) keyval; - - switch (c) { - case ' ': - store_undo_edit_state (); - add_character (keyval); - break; - case 'a': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - select_all (); - } else { - add_character (keyval); - } - break; - case 'c': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - ClipTool.copy_text (this); - } else { - add_character (keyval); - } - break; - case 'v': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - ClipTool.paste_text (this); - store_undo_state_at_next_event = true; - } else { - add_character (keyval); - } - break; - case 'y': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - redo (); - } else { - add_character (keyval); - } - break; - case 'z': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - undo (); - } else { - add_character (keyval); - } - break; - case Key.RIGHT: - check_selection (); - move_carret_next (); - break; - case Key.LEFT: - check_selection (); - move_carret_previous (); - break; - case Key.DOWN: - check_selection (); - move_carret_next_row (); - break; - case Key.UP: - check_selection (); - move_carret_previous_row (); - break; - case Key.END: - check_selection (); - move_carret_to_end_of_line (); - break; - case Key.HOME: - check_selection (); - move_carret_to_beginning_of_line (); - break; - case Key.BACK_SPACE: - if (has_selection ()) { - ui = delete_selected_text (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } else { - ui = remove_last_character (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } - text_changed (get_text ()); - break; - case Key.ENTER: - store_undo_edit_state (); - insert_text ("\n"); - - if (single_line) { - enter (get_text ()); - } - break; - case Key.DEL: - if (has_selection ()) { - ui = delete_selected_text (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } else { - ui = remove_next_character (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } - text_changed (get_text ()); - break; - default: - if (!KeyBindings.has_ctrl () && !KeyBindings.has_logo ()) { - add_character (keyval); - } - break; - } - - GlyphCanvas.redraw (); - } - - void check_selection () { - if (!has_selection () && KeyBindings.has_shift ()) { - show_selection = true; - selection_end = carret.copy (); - } - - if (!KeyBindings.has_shift ()) { - show_selection = false; - } - } - - private void add_character (uint keyval) { - unichar c = (unichar) keyval; - string s; - - if (!is_modifier_key (keyval) - && !KeyBindings.has_ctrl () - && !KeyBindings.has_alt ()) { - - s = (!) c.to_string (); - - if (s.validate ()) { - if (store_undo_state_at_next_event) { - store_undo_edit_state (); - store_undo_state_at_next_event = false; - } - - insert_text (s); - } - } - } - - Paragraph get_current_paragraph () { - Paragraph p; - - if (unlikely (!(0 <= carret.paragraph < paragraphs.size))) { - warning (@"No paragraph, index: $(carret.paragraph), size: $(paragraphs.size)"); - p = new Paragraph ("", 0, 0, text_color); - paragraphs.add (p); - return p; - } - - p = paragraphs.get (carret.paragraph); - return p; - } - - public void set_text (string t) { - int tl; - - if (single_line) { - text = t.replace ("\n", "").replace ("\r", ""); - } else { - text = t; - } - - tl = t.length; - text_length += tl; - - paragraphs.clear (); - generate_paragraphs (); - - return_if_fail (paragraphs.size != 0); - - carret.paragraph = paragraphs.size - 1; - carret.character_index = paragraphs.get (paragraphs.size - 1).text.length; - selection_end = carret.copy (); - show_selection = false; - - text_changed (get_text ()); - } - - Carret get_selection_start () { - if (carret.paragraph == selection_end.paragraph) { - return carret.character_index < selection_end.character_index ? carret : selection_end; - } - - return carret.paragraph < selection_end.paragraph ? carret : selection_end; - } - - Carret get_selection_stop () { - if (carret.paragraph == selection_end.paragraph) { - return carret.character_index > selection_end.character_index ? carret : selection_end; - } - - return carret.paragraph > selection_end.paragraph ? carret : selection_end; - } - - public string get_selected_text () { - Carret selection_start, selection_stop; - int i; - Paragraph pg; - StringBuilder sb; - - sb = new StringBuilder (); - - if (!has_selection ()) { - return "".dup (); - } - - selection_start = get_selection_start (); - selection_stop = get_selection_stop (); - - if (selection_start.paragraph == selection_stop.paragraph) { - pg = paragraphs.get (selection_start.paragraph); - return pg.text.substring (selection_start.character_index, selection_stop.character_index - selection_start.character_index); - } - - pg = paragraphs.get (selection_start.paragraph); - sb.append (pg.text.substring (selection_start.character_index)); - - for (i = selection_start.paragraph + 1; i < selection_stop.paragraph; i++) { - return_val_if_fail (0 <= i < paragraphs.size, "".dup ()); - pg = paragraphs.get (i); - sb.append (pg.text); - } - - pg = paragraphs.get (selection_stop.paragraph); - sb.append (pg.text.substring (0, selection_stop.character_index)); - - return sb.str; - } - - public void select_all () { - while (last_paragraph != DONE) { - generate_paragraphs (); - } - - if (paragraphs.size > 0) { - carret.paragraph = 0; - carret.character_index = 0; - selection_end.paragraph = paragraphs.size - 1; - selection_end.character_index = paragraphs.get (paragraphs.size - 1).text_length; - show_selection = true; - } - } - - public TextUndoItem delete_selected_text () { - Carret selection_start, selection_stop; - int i; - Paragraph pg, pge; - string e, s, n; - bool same; - TextUndoItem ui; - - ui = new TextUndoItem (carret); - - e = ""; - s = ""; - n = ""; - - if (!has_selection ()) { - warning ("No selected text."); - return ui; - } - - selection_start = get_selection_start (); - selection_stop = get_selection_stop (); - - same = selection_start.paragraph == selection_stop.paragraph; - - if (!same) { - return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); - pg = paragraphs.get (selection_start.paragraph); - s = pg.text.substring (0, selection_start.character_index); - - return_val_if_fail (0 <= selection_stop.paragraph < paragraphs.size, ui); - pge = paragraphs.get (selection_stop.paragraph); - e = pge.text.substring (selection_stop.character_index); - - if (!s.has_suffix ("\n")) { - ui.deleted.add (pge.copy ()); - ui.edited.add (pg.copy ()); - - pg.set_text (s + e); - pge.set_text (""); - } else { - ui.edited.add (pg.copy ()); - ui.edited.add (pge.copy ()); - - pg.set_text (s); - pge.set_text (e); - } - } else { - return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); - - pg = paragraphs.get (selection_start.paragraph); - n = pg.text.substring (0, selection_start.character_index); - n += pg.text.substring (selection_stop.character_index); - - if (n == "") { - ui.deleted.add (pg.copy ()); - paragraphs.remove_at (selection_start.paragraph); - } else { - ui.edited.add (pg.copy ()); - } - - pg.set_text (n); - } - - if (e == "" && !same) { - paragraphs.remove_at (selection_stop.paragraph); - } - - for (i = selection_stop.paragraph - 1; i > selection_start.paragraph; i--) { - return_val_if_fail (0 <= i < paragraphs.size, ui); - ui.deleted.add (paragraphs.get (i)); - paragraphs.remove_at (i); - } - - if (s == "" && !same) { - return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); - paragraphs.remove_at (selection_start.paragraph); - } - - carret = selection_start.copy (); - selection_end = carret.copy (); - - show_selection = false; - update_paragraph_index (); - layout (); - - return ui; - } - - void update_paragraph_index () { - int i = 0; - foreach (Paragraph p in paragraphs) { - p.index = i; - i++; - } - } - - public TextUndoItem remove_last_character () { - TextUndoItem ui; - move_carret_previous (); - ui = remove_next_character (); - return ui; - } - - public TextUndoItem remove_next_character () { - Paragraph paragraph; - Paragraph next_paragraph; - int index; - unichar c; - string np; - TextUndoItem ui; - - ui = new TextUndoItem (carret); - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, ui); - paragraph = paragraphs.get (carret.paragraph); - - index = carret.character_index; - - paragraph.text.get_next_char (ref index, out c); - - if (index >= paragraph.text_length) { - np = paragraph.text.substring (0, carret.character_index); - - if (carret.paragraph + 1 < paragraphs.size) { - next_paragraph = paragraphs.get (carret.paragraph + 1); - paragraphs.remove_at (carret.paragraph + 1); - - np = np + next_paragraph.text; - - ui.deleted.add (next_paragraph); - } - - paragraph.set_text (np); - ui.edited.add (paragraph); - } else { - np = paragraph.text.substring (0, carret.character_index) + paragraph.text.substring (index); - paragraph.set_text (np); - - if (np == "") { - return_val_if_fail (carret.paragraph > 0, ui); - carret.paragraph--; - paragraph = paragraphs.get (carret.paragraph); - carret.character_index = paragraph.text_length; - - ui.deleted.add (paragraphs.get (carret.paragraph + 1)); - - paragraphs.remove_at (carret.paragraph + 1); - } else { - ui.edited.add (paragraph); - } - } - - update_paragraph_index (); - layout (); - - return ui; - } - - public void move_carret_next () { - unichar c; - - move_carret_one_character (); - - if (KeyBindings.has_ctrl ()) { - while (true) { - c = move_carret_one_character (); - - if (c == '\0' || c == ' ') { - break; - } - } - } - } - - unichar move_carret_one_character () { - Paragraph paragraph; - int index; - unichar c; - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - paragraph = paragraphs.get (carret.paragraph); - - index = carret.character_index; - - paragraph.text.get_next_char (ref index, out c); - - if (index >= paragraph.text_length && carret.paragraph + 1 < paragraphs.size) { - carret.paragraph++; - carret.character_index = 0; - c = ' '; - } else { - carret.character_index = index; - } - - return c; - } - - public void move_carret_previous () { - unichar c; - - move_carret_back_one_character (); - - if (KeyBindings.has_ctrl ()) { - while (true) { - c = move_carret_back_one_character (); - - if (c == '\0' || c == ' ') { - break; - } - } - } - } - - unichar move_carret_back_one_character () { - Paragraph paragraph; - int index, last_index; - unichar c; - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - paragraph = paragraphs.get (carret.paragraph); - - index = 0; - last_index = -1; - - while (paragraph.text.get_next_char (ref index, out c) && index < carret.character_index) { - last_index = index; - } - - if (last_index <= 0 && carret.paragraph > 0) { - carret.paragraph--; - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - paragraph = paragraphs.get (carret.paragraph); - carret.character_index = paragraph.text_length; - - if (paragraph.text.has_suffix ("\n")) { - carret.character_index -= "\n".length; - } - - c = ' '; - } else if (last_index > 0) { - carret.character_index = last_index; - } else { - carret.character_index = 0; - c = ' '; - } - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - - return c; - } - - public void move_carret_next_row () { - double nr = font_size; - - if (carret.desired_y + 2 * font_size >= allocation.height) { - scroll (2 * font_size); - nr = -font_size; - } - - if (carret.desired_y + nr < widget_y + height - padding) { - carret = get_carret_at (carret.desired_x - widget_x - padding, carret.desired_y + nr); - } - } - - public void move_carret_to_end_of_line () { - carret = get_carret_at (widget_x + padding + width, carret.desired_y, false); - } - - public void move_carret_to_beginning_of_line () { - carret = get_carret_at (widget_x, carret.desired_y, false); - } - - public void move_carret_previous_row () { - double nr = -font_size; - - if (carret.desired_y - 2 * font_size < 0) { - scroll (-2 * font_size); - nr = font_size; - } - - if (carret.desired_y + nr > widget_y + padding) { - carret = get_carret_at (carret.desired_x, carret.desired_y + nr); - } - } - - public bool has_selection () { - return show_selection && selection_is_visible (); - } - - private bool selection_is_visible () { - return carret.paragraph != selection_end.paragraph || carret.character_index != selection_end.character_index; - } - - public void insert_text (string t) { - string s; - Paragraph paragraph; - TextUndoItem ui; - Gee.ArrayList<string> pgs; - bool u = false; - - pgs = new Gee.ArrayList<string> (); - - if (single_line) { - s = t.replace ("\n", "").replace ("\r", ""); - pgs.add (s); - } else { - if (t.last_index_of ("\n") > 0) { - string[] parts = t.split ("\n"); - int i; - for (i = 0; i < parts.length -1; i++) { - pgs.add (parts[i]); - pgs.add ("\n"); - } - - pgs.add (parts[parts.length - 1]); - - if (t.has_suffix ("\n")) { - pgs.add ("\n"); - } - } else { - s = t; - pgs.add (s); - } - } - - if (has_selection () && show_selection) { - ui = delete_selected_text (); - u = true; - - if (paragraphs.size == 0) { - paragraphs.add (new Paragraph ("", font_size, 0, text_color)); - } - } else { - ui = new TextUndoItem (carret); - } - - return_if_fail (0 <= carret.paragraph < paragraphs.size); - paragraph = paragraphs.get (carret.paragraph); - - if (pgs.size > 0) { - if (!u) { - ui.edited.add (paragraph.copy ()); - } - - string first = pgs.get (0); - - string end; - string nt = paragraph.text.substring (0, carret.character_index); - - nt += first; - end = paragraph.text.substring (carret.character_index); - - paragraph.set_text (nt); - - int paragraph_index = carret.paragraph; - Paragraph next_paragraph = paragraph; - for (int i = 1; i < pgs.size; i++) { - paragraph_index++; - string next = pgs.get (i); - next_paragraph = new Paragraph (next, font_size, paragraph_index, text_color); - paragraphs.insert (paragraph_index, next_paragraph); - ui.added.add (next_paragraph); - u = true; - } - - carret.paragraph = paragraph_index; - carret.character_index = next_paragraph.text.length; - - next_paragraph.set_text (next_paragraph.text + end); - } - - if (u) { - undo_items.add (ui); - redo_items.clear (); - } - - update_paragraph_index (); - layout (); - - text_changed (get_text ()); - show_selection = false; - } - - public string get_text () { - StringBuilder sb = new StringBuilder (); - - generate_all_paragraphs (); - - foreach (Paragraph p in paragraphs) { - sb.append (p.text); - } - - return sb.str; - } - - Carret get_carret_at (double click_x, double click_y, - bool check_boundaries = true) { - - int i = 0; - double tx, ty; - double p; - string w; - int ch_index; - double min_d = double.MAX; - Carret c = new Carret (); - double dt; - - c.paragraph = -1; - c.desired_x = click_x; - c.desired_y = click_y; - - foreach (Paragraph paragraph in paragraphs) { - if (!check_boundaries || paragraph.text_is_on_screen (allocation, widget_y)) { - ch_index = 0; - - if (paragraph.start_y + widget_y - font_size <= click_y <= paragraph.end_y + widget_y + font_size) { - foreach (Text next_word in paragraph.words) { - double tt_click = click_y - widget_y - padding + font_size; - - w = next_word.text; - - if (next_word.widget_y <= tt_click <= next_word.widget_y + font_size) { - - p = next_word.get_sidebearing_extent (); - - if ((next_word.widget_y <= tt_click <= next_word.widget_y + font_size) - && (next_word.widget_x + widget_x <= click_x <= next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())) { - - tx = widget_x + next_word.widget_x + padding; - ty = widget_y + next_word.widget_y + padding; - - next_word.iterate ((glyph, kerning, last) => { - double cw; - int ci; - double d; - string gc = (!) glyph.get_unichar ().to_string (); - - d = Math.fabs (click_x - tx); - - if (d <= min_d) { - min_d = d; - c.character_index = ch_index; - c.paragraph = i; - } - - cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; - ci = gc.length; - - tx += cw; - ch_index += ci; - }); - - dt = Math.fabs (click_x - (tx + widget_x + padding)); - if (dt < min_d) { - min_d = dt; - c.character_index = ch_index; - c.paragraph = i; - } - } else { - dt = Math.fabs (click_x - (next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())); - - if (dt < min_d) { - min_d = dt; - c.character_index = ch_index + w.length; - - if (w.has_suffix ("\n")) { - c.character_index -= "\n".length; - } - - c.paragraph = i; - } - - ch_index += w.length; - } - } else { - ch_index += w.length; - } - } - } - } - i++; - } - - if (unlikely (c.paragraph < 0)) { - c.paragraph = paragraphs.size > 0 ? paragraphs.size - 1 : 0; - c.character_index = paragraphs.size > 0 ? paragraphs.get (c.paragraph).text.length : 0; - } - - store_undo_state_at_next_event = true; - - return c; - } - - /** @return offset to click in text. */ - public override void layout () { - double p; - double tx, ty; - string w; - double xmax = 0; - int i = 0; - double dd; - - tx = 0; - ty = font_size; - - if (allocation.width <= 0 || allocation.height <= 0) { - warning ("Parent widget allocation is not set."); - } - - for (i = paragraphs.size - 1; i >= 0 && paragraphs.size > 1; i--) { - if (unlikely (paragraphs.get (i).is_empty ())) { - warning ("Empty paragraph."); - paragraphs.remove_at (i); - update_paragraph_index (); - } - } - - i = 0; - foreach (Paragraph paragraph in paragraphs) { - if (paragraph.need_layout - || (paragraph.text_area_width != width - && paragraph.text_is_on_screen (allocation, widget_y))) { - - paragraph.start_y = ty; - paragraph.start_x = tx; - - paragraph.cached_surface = null; - - foreach (Text next_word in paragraph.words) { - next_word.set_font_size (font_size); - - w = next_word.text; - p = next_word.get_sidebearing_extent (); - - if (unlikely (p == 0)) { - warning (@"Zero width word: $(w)"); - } - - if (w == "") { - break; - } - - if (w == "\n") { - next_word.widget_x = tx; - next_word.widget_y = ty; - - tx = 0; - ty += next_word.font_size; - } else { - if (!single_line) { - if (tx + p + 2 * padding > width || w == "\n") { - tx = 0; - ty += next_word.font_size; - } - } - - if (tx + p > xmax) { - xmax = tx + p; - } - - next_word.widget_x = tx; - next_word.widget_y = ty; - - if (w != "\n") { - tx += p; - } - } - } - - if (tx > xmax) { - xmax = tx; - } - - paragraph.text_area_width = width; - paragraph.width = xmax; - paragraph.end_x = tx; - paragraph.end_y = ty; - paragraph.need_layout = false; - } - - if (xmax > width) { - break; - } - - tx = paragraph.end_x; - ty = paragraph.end_y; - i++; - } - - if (xmax > width) { - this.width = xmax + 2 * padding; - layout (); - return; - } - - this.height = fmax (min_height, ty + 2 * padding); - - if (last_paragraph != DONE) { - this.height = (text_length / (double) last_paragraph) * ty + 2 * padding; // estimate height - } - - if (ty + widget_y < allocation.height && last_paragraph != DONE) { - generate_paragraphs (); - layout (); - return; - } - - ty = font_size; - tx = 0; - - foreach (Paragraph paragraph in paragraphs) { - dd = ty - paragraph.start_y; - - if (dd != 0) { - paragraph.start_y += dd; - paragraph.end_y += dd; - foreach (Text word in paragraph.words) { - word.widget_y += dd; - } - } - - ty = paragraph.end_y; - } - } - - public override void button_press (uint button, double x, double y) { - if (is_over (x, y)) { - carret = get_carret_at (x, y); - selection_end = carret.copy (); - update_selection = true; - } - } - - public override void button_release (uint button, double x, double y) { - update_selection = false; - show_selection = selection_is_visible (); - } - - public override bool motion (double x, double y) { - if (update_selection) { - selection_end = get_carret_at (x, y); - show_selection = selection_is_visible (); - } - - return update_selection; - } - - public override void draw (Context cr) { - Text word; - double tx, ty; - string w; - double scale; - double width; - double x = widget_x; - double y = widget_y; - Carret selection_start, selection_stop; - double carret_x; - double carret_y; - - layout (); - - if (draw_border) { - // background - cr.save (); - cr.set_line_width (1); - Theme.color (cr, "Text Area Background"); - draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); - cr.fill (); - cr.restore (); - - // border - cr.save (); - cr.set_line_width (1); - Theme.color (cr, "Foreground 1"); - draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); - cr.stroke (); - cr.restore (); - } - - cr.save (); - - word = new Text (); - - width = this.width - padding; - x += padding; - scale = word.get_font_scale (); - y += font_size; - - // draw selection background - if (has_selection ()) { - tx = 0; - ty = 0; - - selection_start = get_selection_start (); - selection_stop = get_selection_stop (); - - cr.save (); - Theme.color (cr, "Highlighted 1"); - - for (int i = selection_start.paragraph; i <= selection_stop.paragraph; i++) { - return_if_fail (0 <= i < paragraphs.size); - Paragraph pg = paragraphs.get (i); - - if (pg.text_is_on_screen (allocation, widget_y)) { - int char_index = 0; - - foreach (Text next_word in pg.words) { - double cw = next_word.get_sidebearing_extent (); - bool paint_background = false; - bool partial_start = false; - bool partial_stop = false; - int wl; - - w = next_word.text; - wl = w.length; - scale = next_word.get_font_scale (); - - if (selection_start.paragraph == selection_stop.paragraph) { - partial_start = true; - partial_stop = true; - } else if (selection_start.paragraph < i < selection_stop.paragraph) { - paint_background = true; - } else if (selection_start.paragraph == i) { - paint_background = true; - partial_start = true; - } else if (selection_stop.paragraph == i) { - paint_background = char_index + wl < selection_stop.character_index; - partial_stop = !paint_background; - } - - if (paint_background && !(partial_start || partial_stop)) { - double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; - cr.rectangle (widget_x + padding + next_word.widget_x - 1, selection_y, cw + 1, font_size); - cr.fill (); - } - - if (partial_start || partial_stop) { - int index = char_index; - double bx = widget_x + padding + next_word.widget_x + (partial_start ? 0 : 1); - - next_word.iterate ((glyph, kerning, last) => { - double cwi; - int ci; - bool draw = (index >= selection_start.character_index && partial_start && !partial_stop) - || (index < selection_stop.character_index && !partial_start && partial_stop) - || (selection_start.character_index <= index < selection_stop.character_index && partial_start && partial_stop); - - cwi = (glyph.get_width ()) * next_word.get_font_scale () + kerning; - - if (draw) { - double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; - cr.rectangle (bx - 1, selection_y, cwi + 1, font_size); - cr.fill (); - } - - bx += cwi; - ci = ((!) glyph.get_unichar ().to_string ()).length; - index += ci; - }); - } - - char_index += w.length; - } - } - } - - cr.restore (); - } - - tx = 0; - ty = 0; - - int first_visible = 0; - int last_visible; - int paragraphs_size = paragraphs.size; - while (first_visible < paragraphs_size) { - if (paragraphs.get (first_visible).text_is_on_screen (allocation, widget_y)) { - break; - } - first_visible++; - } - - last_visible = first_visible; - while (last_visible < paragraphs_size) { - if (!paragraphs.get (last_visible).text_is_on_screen (allocation, widget_y)) { - last_visible++; - break; - } - last_visible++; - } - - if (paragraphs_size == 0) { - if (carret_is_visible) { - draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); - } - - return; - } - - Context cc; // cached context - Paragraph paragraph; - paragraph = paragraphs.get (0); - - tx = paragraph.start_x; - ty = paragraph.start_y; - - for (int i = first_visible; i < last_visible; i++) { - paragraph = paragraphs.get (i); - - tx = paragraph.start_x; - ty = paragraph.start_y; - - if (paragraph.cached_surface == null) { - paragraph.cached_surface = Screen.create_background_surface ((int) width + 2, paragraph.get_height () + (int) font_size + 2); - cc = new Context ((!) paragraph.cached_surface); - cc.scale (Screen.get_scale(), Screen.get_scale()); - - foreach (Text next_word in paragraph.words) { - if (next_word.text != "\n") { - next_word.draw_at_top (cc, next_word.widget_x, next_word.widget_y - ty); - } - } - } - - if (likely (paragraph.cached_surface != null)) { - // FIXME: subpixel offset in text area - Screen.paint_background_surface(cr, - (!) paragraph.cached_surface, - (int) (x + tx), - (int) (widget_y + paragraph.start_y - font_size + padding)); - } else { - warning ("No paragraph image."); - } - } - - if (carret_is_visible) { - get_carret_position (carret, out carret_x, out carret_y); - - if (carret_y < 0) { - draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); - } else { - draw_carret_at (cr, carret_x, carret_y); - } - } - - if (has_selection ()) { - get_carret_position (selection_end, out carret_x, out carret_y); - - if (carret_y < 0) { - draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); - } else { - draw_carret_at (cr, carret_x, carret_y); - } - } - } - - void get_carret_position (Carret carret, out double carret_x, out double carret_y) { - Paragraph paragraph; - double tx; - double ty; - int ch_index; - int wl; - double pos_x, pos_y; - - ch_index = 0; - - carret_x = -1; - carret_y = -1; - - return_if_fail (0 <= carret.paragraph < paragraphs.size); - paragraph = paragraphs.get (carret.paragraph); - - pos_x = -1; - pos_y = -1; - - foreach (Text next_word in paragraph.words) { - string w = next_word.text; - wl = w.length; - - if (carret.character_index == ch_index) { - pos_x = next_word.widget_x + widget_x + padding; - pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); - } else if (carret.character_index >= ch_index + wl) { - pos_x = next_word.widget_x + next_word.get_sidebearing_extent () + widget_x + padding; - pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); - - if (next_word.text.has_suffix ("\n")) { - pos_x = widget_x + padding; - pos_y += next_word.font_size; - } - } else if (ch_index < carret.character_index <= ch_index + wl) { - tx = widget_x + next_word.widget_x; - ty = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); - - if (carret.character_index <= ch_index) { - pos_x = widget_x + padding; - pos_y = ty; - } - - next_word.iterate ((glyph, kerning, last) => { - double cw; - int ci; - - cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; - ci = ((!) glyph.get_unichar ().to_string ()).length; - - if (ch_index < carret.character_index <= ch_index + ci) { - pos_x = tx + cw + padding; - pos_y = ty; - - if (glyph.get_unichar () == '\n') { - pos_x = widget_x + padding; - pos_y += next_word.font_size; - } - } - - tx += cw; - ch_index += ci; - }); - } - - ch_index += wl; - } - - carret_x = pos_x; - carret_y = pos_y; - } - - void draw_carret_at (Context cr, double x, double y) { - cr.save (); - cr.set_source_rgba (0, 0, 0, 0.5); - cr.set_line_width (1); - cr.move_to (x, y); - cr.line_to (x, y - font_size); - cr.stroke (); - cr.restore (); - } - - public void store_undo_edit_state () { - TextUndoItem ui = new TextUndoItem (carret); - ui.edited.add (get_current_paragraph ().copy ()); - undo_items.add (ui); - redo_items.clear (); - } - - public void redo () { - TextUndoItem i; - TextUndoItem undo_item; - - if (redo_items.size > 0) { - i = redo_items.get (redo_items.size - 1); - - undo_item = new TextUndoItem (i.carret); - - i.deleted.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pb.index - pa.index; - }); - - i.added.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pa.index - pb.index; - }); - - foreach (Paragraph p in i.deleted) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning ("Paragraph not found."); - } else { - undo_item.deleted.add (p.copy ()); - paragraphs.remove_at (p.index); - } - } - - foreach (Paragraph p in i.added) { - if (p.index == paragraphs.size) { - paragraphs.add (p.copy ()); - } else { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); - } else { - undo_item.added.add (paragraphs.get (p.index).copy ()); - paragraphs.insert (p.index, p.copy ()); - } - } - } - - foreach (Paragraph p in i.edited) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); - return; - } - - undo_item.edited.add (paragraphs.get (p.index).copy ()); - paragraphs.set (p.index, p.copy ()); - } - - redo_items.remove_at (redo_items.size - 1); - undo_items.add (undo_item); - - carret = i.carret.copy (); - layout (); - } - } - - public void undo () { - TextUndoItem i; - TextUndoItem redo_item; - - if (undo_items.size > 0) { - i = undo_items.get (undo_items.size - 1); - redo_item = new TextUndoItem (i.carret); - - i.deleted.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pa.index - pb.index; - }); - - i.added.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pb.index - pa.index; - }); - - foreach (Paragraph p in i.added) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning ("Paragraph not found."); - } else { - redo_item.added.add (paragraphs.get (p.index).copy ()); - paragraphs.remove_at (p.index); - } - } - - foreach (Paragraph p in i.deleted) { - if (p.index == paragraphs.size) { - paragraphs.add (p.copy ()); - } else { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); - } else { - redo_item.deleted.add (p.copy ()); - paragraphs.insert (p.index, p.copy ()); - } - } - } - - foreach (Paragraph p in i.edited) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); - return; - } - - redo_item.edited.add (paragraphs.get (p.index).copy ()); - paragraphs.set (p.index, p.copy ()); - } - - undo_items.remove_at (undo_items.size - 1); - redo_items.add (redo_item); - - carret = i.carret.copy (); - layout (); - } - } - - public void set_editable (bool editable) { - this.editable = editable; - } - - public class TextUndoItem : GLib.Object { - public Carret carret; - public Gee.ArrayList<Paragraph> added = new Gee.ArrayList<Paragraph> (); - public Gee.ArrayList<Paragraph> edited = new Gee.ArrayList<Paragraph> (); - public Gee.ArrayList<Paragraph> deleted = new Gee.ArrayList<Paragraph> (); - - public TextUndoItem (Carret c) { - carret = c.copy (); - } - } - - public class Paragraph : GLib.Object { - public double end_x = -10000; - public double end_y = -10000; - - public double start_x = -10000; - public double start_y = -10000; - - public double width = -10000; - public double text_area_width = -10000; - - public string text; - - public Gee.ArrayList<Text> words { - get { - if (words_in_paragraph.size == 0) { - generate_words (); - } - - return words_in_paragraph; - } - } - - private Gee.ArrayList<Text> words_in_paragraph = new Gee.ArrayList<Text> (); - public int text_length; - public bool need_layout = true; - public Surface? cached_surface = null; - double font_size; - public int index; - Color text_color; - - public Paragraph (string text, double font_size, int index, Color c) { - this.index = index; - this.font_size = font_size; - text_color = c; - set_text (text); - } - - public Paragraph copy () { - Paragraph p = new Paragraph (text.dup (), font_size, index, text_color); - p.need_layout = true; - return p; - } - - public bool is_empty () { - return text == ""; - } - - public void set_text (string t) { - this.text = t; - text_length = t.length; - need_layout = true; - words.clear (); - cached_surface = null; - } - - public int get_height () { - return (int) (end_y - start_y) + 1; - } - - public int get_width () { - return (int) width + 1; - } - - public bool text_is_on_screen (WidgetAllocation alloc, double widget_y) { - bool v = (0 <= start_y + widget_y <= alloc.height) - || (0 <= end_y + widget_y <= alloc.height) - || (start_y + widget_y <= 0 && alloc.height <= end_y + widget_y); - return v; - } - - private void generate_words () { - string w; - int p = 0; - bool carret_at_word_end = false; - Text word; - int carret = 0; - int iter_pos = 0; - - return_if_fail (words_in_paragraph.size == 0); - - while (p < text_length) { - w = get_next_word (out carret_at_word_end, ref iter_pos, carret); - - if (w == "") { - break; - } - - word = new Text (w, font_size); - - word.r = text_color.r; - word.g = text_color.g; - word.b = text_color.b; - word.a = text_color.a; - - words_in_paragraph.add (word); - } - } - - string get_next_word (out bool carret_at_end_of_word, ref int iter_pos, int carret) { - int i; - int ni; - int pi; - string n; - int nl; - - carret_at_end_of_word = false; - - if (iter_pos >= text_length) { - carret_at_end_of_word = true; - return "".dup (); - } - - if (text.get_char (iter_pos) == '\n') { - iter_pos += "\n".length; - carret_at_end_of_word = (iter_pos == carret); - return "\n".dup (); - } - - i = text.index_of (" ", iter_pos); - pi = i + " ".length; - - ni = text.index_of ("\t", iter_pos); - if (ni != -1 && ni < pi || i == -1) { - i = ni; - pi = i + "\t".length; - } - - ni = text.index_of ("\n", iter_pos); - if (ni != -1 && ni < pi || i == -1) { - i = ni; - pi = i; - } - - if (iter_pos + iter_pos - pi > text_length || i == -1) { - n = text.substring (iter_pos); - } else { - n = text.substring (iter_pos, pi - iter_pos); - } - - nl = n.length; - if (iter_pos < carret < iter_pos + nl) { - n = text.substring (iter_pos, carret - iter_pos); - nl = n.length; - carret_at_end_of_word = true; - } - - iter_pos += nl; - - if (iter_pos == carret) { - carret_at_end_of_word = true; - } - - return n; - } - } - - public class Carret : GLib.Object { - - public int paragraph = 0; - - public int character_index { - get { - return ci; - } - - set { - ci = value; - } - } - - private int ci = 0; - - public double desired_x = 0; - public double desired_y = 0; - - public Carret () { - } - - public void print () { - stdout.printf (@"paragraph: $paragraph, character_index: $character_index\n"); - } - - public Carret copy () { - Carret c = new Carret (); - - c.paragraph = paragraph; - c.character_index = character_index; - - c.desired_x = desired_x; - c.desired_y = desired_y; - - return c; - } - } - } - - }
diff --git libbirdfont/Renderer/fontconfig.c(deleted)
--- a/libbirdfont/Renderer/fontconfig.c +++ /dev/null @@ -1,141 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - 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 3 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. - */ - - #include <stdio.h> - #include <glib.h> - #include <fontconfig/fontconfig.h> - - gchar* find_font_with_property (FcConfig* fontconfig, const gchar* characters, const gchar* property) { - FcPattern* pattern; - FcCharSet* character_set; - FcObjectSet* font_properties; - FcFontSet* fonts; - FcPattern* font; - FcChar8* path; - gchar* result; - gchar* remaining_characters; - gunichar character; - - if (fontconfig == NULL) { - g_warning("Font config not loaded."); - return NULL; - } - - result = NULL; - pattern = FcPatternCreate (); - - character_set = FcCharSetCreate (); - - remaining_characters = (gchar*) characters; - while (TRUE) { - character = g_utf8_get_char (remaining_characters); - - if (character == '\0') { - break; - } - - FcCharSetAddChar(character_set, character); - - remaining_characters = g_utf8_next_char (remaining_characters); - } - - FcPatternAddCharSet (pattern, FC_CHARSET, character_set); - FcCharSetDestroy (character_set); - FcPatternAddInteger (pattern, FC_SLANT, FC_SLANT_ROMAN); - - FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); - font_properties = FcObjectSetBuild (property, NULL); - fonts = FcFontList (fontconfig, pattern, font_properties); - - if (fonts && fonts->nfont > 0) { - font = fonts->fonts[0]; - if (FcPatternGetString(font, property, 0, &path) == FcResultMatch) { - result = g_strdup ((gchar*) path); - } - } - - if (fonts) { - FcFontSetDestroy(fonts); - } - - if (pattern) { - FcPatternDestroy(pattern); - } - - return result; - } - - /** Find a fallback font for a set of characters. - * @return A path to the font file. - */ - gchar* find_font (FcConfig* fontconfig, const gchar* characters) { - return find_font_with_property (fontconfig, characters, FC_FILE); - } - - /** Find a fallback font for a set of characters. - * @return Family name of the font. - */ - gchar* find_font_family (FcConfig* fontconfig, const gchar* characters) { - return find_font_with_property (fontconfig, characters, FC_FAMILY); - } - - /** Find a font file from its family name. - * @param font_config fontconfig instance - * @param font_name name of the font - * @return full path to the font file - */ - gchar* find_font_file (FcConfig* font_config, const gchar* font_name) { - const FcChar8* name; - FcPattern* search_pattern; - FcPattern* font; - FcChar8* file; - gchar* path; - FcObjectSet* font_properties; - FcFontSet* fonts; - int i; - - if (font_config == NULL) { - g_warning("Font config not loaded."); - return NULL; - } - - path = NULL; - name = font_name; - - search_pattern = FcPatternCreate (); - FcPatternAddString (search_pattern, FC_FAMILY, name); - FcPatternAddBool (search_pattern, FC_SCALABLE, FcTrue); - FcPatternAddInteger (search_pattern, FC_WEIGHT, FC_WEIGHT_MEDIUM); - FcPatternAddInteger (search_pattern, FC_SLANT, FC_SLANT_ROMAN); - - font_properties = FcObjectSetBuild (FC_FILE, NULL); - fonts = FcFontList (font_config, search_pattern, font_properties); - - if (fonts->nfont > 0) { - for (i = 0; i < fonts->nfont; i++) { - font = fonts->fonts[i]; - - if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { - path = g_strdup ((gchar*) file); - break; - } - } - FcPatternDestroy (font); - } - - FcPatternDestroy (search_pattern); - - return path; - }
--- a/libbirdfont/ResizeTool.vala +++ b/libbirdfont/ResizeTool.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 2015 Johan Mattsson + Copyright (C) 2013 2015 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -14,6 +14,7 @@ using Math; using Cairo; + using SvgBird; namespace BirdFont { @@ -21,7 +22,7 @@ bool resize_path_proportional = false; bool resize_width = false; - Path? resized_path = null; + SvgBird.Object? resized_path = null; double last_resize_y; double last_resize_x; @@ -31,11 +32,18 @@ static double selection_box_height = 0; static double selection_box_center_x = 0; static double selection_box_center_y = 0; - + static double selection_box_left = 0; + static double selection_box_top = 0; + + static double resized_width = 0; + static double resized_height = 0; + static double resized_top = 0; + static double resized_left = 0; + static bool rotate_path = false; static double last_rotate_y; static double rotation = 0; - static double last_rotate = 0; + public static double last_rotate = 0; public double last_skew = 0; @@ -55,6 +63,9 @@ horizontal_handle = new Text ("resize_handle_horizontal", 60); horizontal_handle.load_font ("icons.bf"); Theme.text_color (horizontal_handle, "Highlighted 1"); + + DrawingTools.move_tool.selection_changed.connect (update_position); + DrawingTools.move_tool.objects_moved.connect (update_position); select_action.connect((self) => { }); @@ -63,13 +74,13 @@ }); press_action.connect((self, b, x, y) => { - Path last_path; + SvgBird.Object last_path; Glyph glyph; glyph = MainWindow.get_current_glyph (); glyph.store_undo_state (); - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { if (is_over_resize_handle (p, x, y)) { resize_path_proportional = true; resized_path = p; @@ -87,7 +98,7 @@ } } - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { if (is_over_rotate_handle (p, x, y)) { rotate_path = true; return; @@ -96,7 +107,7 @@ if (glyph.active_paths.size > 0) { last_path = glyph.active_paths.get (glyph.active_paths.size - 1); - last_rotate = last_path.rotation; + last_rotate = last_path.transforms.rotation; } rotation = last_rotate; @@ -121,8 +132,12 @@ update_selection_box (); GlyphCanvas.redraw (); - foreach (Path p in MainWindow.get_current_glyph ().active_paths) { - p.create_full_stroke (); + foreach (SvgBird.Object object in MainWindow.get_current_glyph ().active_paths) { + if (object is PathObject) { + PathObject path = (PathObject) object; + Path p = path.get_path (); + p.create_full_stroke (); + } } }); @@ -131,17 +146,14 @@ if (resize_path_proportional && can_resize (x, y)) { resize_proportional (x, y); - update_selection_box (); } if (resize_width && can_resize (x, y)) { resize_horizontal (x, y); - update_selection_box (); } if (rotate_path) { rotate (x, y); - update_selection_box (); } if (move_paths @@ -149,12 +161,7 @@ || resize_path_proportional || resize_width) { - glyph = MainWindow.get_current_glyph (); - - foreach (Path selected_path in glyph.active_paths) { - selected_path.reset_stroke (); - } - + glyph = MainWindow.get_current_glyph (); GlyphCanvas.redraw (); } @@ -165,53 +172,74 @@ Text handle; Glyph g = MainWindow.get_current_glyph (); - if (!rotate_path) { - if (!resize_width) { - handle = proportional_handle; - get_resize_handle_position (out handle.widget_x, out handle.widget_y); + if (!move_paths) { + if (!rotate_path) { + if (!resize_width) { + handle = proportional_handle; + get_resize_handle_position (out handle.widget_x, out handle.widget_y); + + handle.widget_x -= handle.get_sidebearing_extent () / 2; + handle.widget_y -= handle.get_height () / 2; + + handle.draw (cr); + } - handle.widget_x -= handle.get_sidebearing_extent () / 2; - handle.widget_y -= handle.get_height () / 2; - - handle.draw (cr); - } - - if (!resize_path_proportional) { - handle = horizontal_handle; + if (!resize_path_proportional) { + handle = horizontal_handle; + + get_horizontal_reseize_handle_position (out handle.widget_x, + out handle.widget_y); + + handle.widget_x -= handle.get_sidebearing_extent () / 2; + handle.widget_y -= handle.get_height () / 2; + + handle.draw (cr); + } + } + + if (!resize_path_proportional && !resize_width + && g.active_paths.size > 0) { - get_horizontal_reseize_handle_position (out handle.widget_x, - out handle.widget_y); - - handle.widget_x -= handle.get_sidebearing_extent () / 2; - handle.widget_y -= handle.get_height () / 2; - - handle.draw (cr); + draw_rotate_handle (cr); } } - - if (!resize_path_proportional && !resize_width - && g.active_paths.size > 0) { - - draw_rotate_handle (cr); - } - + MoveTool.draw_actions (cr); }); key_press_action.connect ((self, keyval) => { DrawingTools.move_tool.key_down (keyval); }); + } + + public void update_position () { + Glyph glyph = MainWindow.get_current_glyph (); + SvgBird.Object path; + + if (glyph.active_paths.size > 0) { + path = glyph.active_paths.get (glyph.active_paths.size - 1); + + if (path is PathObject) { + last_rotate = ((PathObject) path).get_path ().rotation; + } else { + last_rotate = path.transforms.total_rotation; + } + } + + rotation = last_rotate; + update_selection_box (); + update_resized_boundaries (); } public static void get_resize_handle_position (out double px, out double py) { - px = Glyph.reverse_path_coordinate_x (selection_box_center_x + selection_box_width / 2); - py = Glyph.reverse_path_coordinate_y (selection_box_center_y + selection_box_height / 2); + px = Glyph.reverse_path_coordinate_x (resized_left + resized_width); + py = Glyph.reverse_path_coordinate_y (-resized_top); } public static void get_horizontal_reseize_handle_position (out double px, out double py) { - px = Glyph.reverse_path_coordinate_x (selection_box_center_x + selection_box_width / 2); + px = Glyph.reverse_path_coordinate_x (resized_left + resized_width); px += 40; - py = Glyph.reverse_path_coordinate_y (selection_box_center_y); + py = Glyph.reverse_path_coordinate_y (-resized_top - resized_height / 2); } public static double get_rotated_handle_length () { @@ -225,69 +253,66 @@ hy = sin (rotation) * d; return d; - } - - public void signal_objects_rotated () { - objects_rotated (rotation * (180 / PI)); } public void rotate_selected_paths (double angle, double cx, double cy) { Glyph glyph = MainWindow.get_current_glyph (); - double dx, dy, xc2, yc2, w, h; - Path last_path; + double x, y; - foreach (Path p in glyph.active_paths) { - p.rotate (angle, cx, cy); + glyph.layers.update_boundaries_for_object (); + + foreach (SvgBird.Object p in glyph.active_paths) { + if (p is EmbeddedSvg) { + EmbeddedSvg svg = (EmbeddedSvg) p; + x = selection_box_left - svg.x + selection_box_width / 2; + y = selection_box_top + svg.y + selection_box_height / 2; + double theta = angle - svg.drawing.transforms.rotation; + svg.drawing.transforms.rotate (theta, x, y); + svg.drawing.transforms.collapse_transforms (); + } else if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + SvgTransforms transform = new SvgTransforms (); + transform.rotate (-angle, selection_box_center_x, selection_box_center_y); + Matrix matrix = transform.get_matrix (); + path.transform (matrix); + path.rotation += angle; + rotation = path.rotation; + path.reset_stroke (); + } } - MoveTool.get_selection_box_boundaries (out xc2, out yc2, out w, out h); + last_rotate = rotation; - dx = -(xc2 - cx); - dy = -(yc2 - cy); - - foreach (Path p in glyph.active_paths) { - p.move (dx, dy); - } - - last_rotate = rotation; - - MoveTool.update_selection_boundaries (); - if (glyph.active_paths.size > 0) { - last_path = glyph.active_paths.get (glyph.active_paths.size - 1); - rotation = last_path.rotation; - if (rotation > PI) { rotation -= 2 * PI; } - last_rotate = rotation; - signal_objects_rotated (); + if (last_rotate > PI) { + last_rotate -= 2 * PI; + } + + objects_rotated (rotation * (180 / PI)); } } /** Move rotate handle to pixel x,y. */ void rotate (double x, double y) { - double cx, cy, xc, yc, a, b; + double a, b; - cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); - cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); - xc = selection_box_center_x; - yc = selection_box_center_y; + a = Glyph.path_coordinate_x (x) - selection_box_center_x; + b = selection_box_center_y - Glyph.path_coordinate_y (y); - a = x - cx; - b = y - cy; - - rotation = atan (b / a); - - if (a < 0) { - rotation += PI; + rotation = atan2 (b, a); + + if (a == 0) { + rotation = b > 0 ? PI / 2 : -PI / 2; } rotate_selected_paths (rotation - last_rotate, selection_box_center_x, selection_box_center_y); } - static bool is_over_rotate_handle (Path p, double x, double y) { + static bool is_over_rotate_handle (SvgBird.Object p, double x, double y) { double cx, cy, hx, hy; double size = 10; bool inx, iny; @@ -315,8 +340,8 @@ cr.rectangle (cx - 2.5, cy - 2.5, 5, 5); cr.fill (); - hx = cos (rotation) * get_rotated_handle_length (); - hy = sin (rotation) * get_rotated_handle_length (); + hx = cos (last_rotate) * get_rotated_handle_length (); + hy = sin (last_rotate) * get_rotated_handle_length (); cr.set_line_width (1); cr.move_to (cx, cy); @@ -356,39 +381,40 @@ public void resize_glyph (Glyph glyph, double ratio_x, double ratio_y, bool selected = true) { - - double resize_pos_x = 0; - double resize_pos_y = 0; - double selection_minx, selection_miny, dx, dy; - + if (!selected) { glyph.clear_active_paths (); - foreach (Path path in glyph.get_visible_paths ()) { - glyph.add_active_path (null, path); + foreach (SvgBird.Object path in glyph.get_visible_objects ()) { + glyph.add_active_object (path); } } - get_selection_min (out resize_pos_x, out resize_pos_y); - - // resize paths - foreach (Path selected_path in glyph.active_paths) { - selected_path.resize (ratio_x, ratio_y); - selected_path.reset_stroke (); - } - - // move paths relative to the updated xmin and xmax - get_selection_min (out selection_minx, out selection_miny); - dx = resize_pos_x - selection_minx; - dy = resize_pos_y - selection_miny; - - foreach (Path selected_path in glyph.active_paths) { - selected_path.move (dx, dy); + foreach (SvgBird.Object p in glyph.active_paths) { + if (p is EmbeddedSvg) { + EmbeddedSvg svg = (EmbeddedSvg) p; + x = selection_box_left - svg.x; + y = selection_box_top + svg.y + selection_box_height; + svg.drawing.transforms.resize (ratio_x, ratio_y, x, y); + svg.drawing.transforms.collapse_transforms (); + svg.drawing.update_view_matrix (); + svg.drawing.update_boundaries_for_object (); + } else if (p is PathObject) { + Path path = ((PathObject) p).get_path (); + x = selection_box_center_x - selection_box_width / 2; + y = selection_box_center_y - selection_box_height / 2; + SvgTransforms transform = new SvgTransforms (); + transform.resize (ratio_x, ratio_y, x, y); + Matrix matrix = transform.get_matrix (); + path.transform (matrix); + path.reset_stroke (); + } } if (glyph.active_paths.size > 0) { - update_selection_box (); - objects_resized (selection_box_width, selection_box_height); + glyph.layers.update_boundaries_for_object (); + update_resized_boundaries (); + objects_resized (resized_width, resized_height); } if (!selected) { @@ -402,11 +428,44 @@ } } - void update_selection_box () { + public static void update_selection_box () { + update_resized_boundaries (); MoveTool.update_boundaries_for_selection (); MoveTool.get_selection_box_boundaries (out selection_box_center_x, out selection_box_center_y, out selection_box_width, - out selection_box_height); + out selection_box_height, out selection_box_left, out selection_box_top); + } + + static void update_resized_boundaries () { + Glyph glyph = MainWindow.get_current_glyph (); + + double left = 10000; + double top = 10000; + double bottom = -10000; + double right = -10000; + + foreach (SvgBird.Object o in glyph.active_paths) { + if (top > o.top) { + top = o.top; + } + + if (left > o.left) { + left = o.left; + } + + if (right < o.right) { + right = o.right; + } + + if (bottom < o.bottom) { + bottom = o.bottom; + } + } + + resized_top = top; + resized_left = left; + resized_width = right - left; + resized_height = bottom - top; } /** Move resize handle to pixel x,y. */ @@ -436,59 +495,6 @@ resize_selected_paths (ratio, 1); last_resize_x = px; last_resize_y = py; - } - } - - public void full_height () { - double xc, yc, w, h; - Glyph glyph = MainWindow.get_current_glyph (); - Font font = BirdFont.get_current_font (); - - MoveTool.update_boundaries_for_selection (); - MoveTool.get_selection_box_boundaries (out xc, out yc, out w, out h); - - //compute scale - double descender = font.base_line - (yc - h / 2); - - if (descender < 0) { - descender = 0; - } - - double font_height = font.top_position - font.base_line; - double scale = font_height / (h - descender); - - resize_selected_paths (scale, scale); - PenTool.reset_stroke (); - - MoveTool.update_boundaries_for_selection (); - font.touch (); - - MoveTool.get_selection_box_boundaries (out selection_box_center_x, - out selection_box_center_y, - out selection_box_width, - out selection_box_height); - - DrawingTools.move_tool.move_to_baseline (); - - foreach (Path path in glyph.active_paths) { - path.move (0, -descender * scale); - } - - objects_resized (selection_box_width, selection_box_height); - } - - void get_selection_min (out double x, out double y) { - Glyph glyph = MainWindow.get_current_glyph (); - x = double.MAX; - y = double.MAX; - foreach (Path p in glyph.active_paths) { - if (p.xmin < x) { - x = p.xmin; - } - - if (p.ymin < y) { - y = p.ymin; - } } } @@ -497,11 +503,11 @@ double h, w; double ratio = get_resize_ratio (x, y); - foreach (Path selected_path in glyph.active_paths) { + foreach (SvgBird.Object selected_path in glyph.active_paths) { h = selected_path.ymax - selected_path.ymin; w = selected_path.xmax - selected_path.xmin; - if (selected_path.points.size <= 1) { + if (selected_path.is_empty ()) { // FIXME: test with one point continue; } @@ -513,13 +519,13 @@ return true; } - bool is_over_resize_handle (Path p, double x, double y) { + bool is_over_resize_handle (SvgBird.Object p, double x, double y) { double handle_x, handle_y; get_resize_handle_position (out handle_x, out handle_y); return Path.distance (handle_x, x, handle_y, y) < 12 * MainWindow.units; } - bool is_over_horizontal_resize_handle (Path p, double x, double y) { + bool is_over_horizontal_resize_handle (SvgBird.Object p, double x, double y) { double handle_x, handle_y; get_horizontal_reseize_handle_position (out handle_x, out handle_y); return Path.distance (handle_x, x, handle_y, y) < 12 * MainWindow.units; @@ -540,26 +546,29 @@ if (!selected_paths) { glyph.clear_active_paths (); - foreach (Path path in glyph.get_visible_paths ()) { - glyph.add_active_path (null, path); + foreach (SvgBird.Object path in glyph.get_visible_objects ()) { + glyph.add_active_object (path); } } glyph.selection_boundaries (out x, out y, out w, out h); - foreach (Path path in glyph.active_paths) { - SvgParser.apply_matrix (path, 1, 0, s, 1, 0, 0); - path.skew = skew; - path.update_region_boundaries (); + foreach (SvgBird.Object path in glyph.active_paths) { + if (path is PathObject) { // FIXME: other objects + Path p = ((PathObject) path).get_path (); + SvgParser.apply_matrix (p, 1, 0, s, 1, 0, 0); + p.skew = skew; + p.reset_stroke (); + path.update_boundaries_for_object (); + } } glyph.selection_boundaries (out nx, out y, out nw, out h); dx = -(nx - x); - foreach (Path p in glyph.active_paths) { + foreach (SvgBird.Object p in glyph.active_paths) { p.move (dx, 0); - p.reset_stroke (); } dw = (nw - w);
--- a/libbirdfont/SaveCallback.vala +++ b/libbirdfont/SaveCallback.vala @@ -28,7 +28,7 @@ } public void save_as () { - if (MenuTab.has_suppress_event ()) { + if (unlikely (MenuTab.has_suppress_event ())) { warn_if_test ("Event suppressed"); return; } @@ -54,6 +54,7 @@ file_name = @"$(f)"; file = File.new_for_path (file_name); font_file_path = (!) file.get_path (); + if (!file.query_exists ()) { save (); } else {
--- a/libbirdfont/SearchPaths.vala +++ b/libbirdfont/SearchPaths.vala @@ -94,7 +94,7 @@ f = get_file (@"/usr/share/birdfont/" + d + "/", name); if (likely (f.query_exists ())) return f; - return f; + return f; } public static string get_locale_directory () {
--- a/libbirdfont/SettingsTab.vala +++ b/libbirdfont/SettingsTab.vala @@ -45,13 +45,13 @@ stroke_width.new_value_action.connect ((self) => { Glyph g = MainWindow.get_current_glyph (); - Path.stroke_width = stroke_width.get_value (); + CanvasSettings.stroke_width = stroke_width.get_value (); g.redraw_area (0, 0, g.allocation.width, g.allocation.height); Preferences.set ("stroke_width_for_open_paths", stroke_width.get_display_value ()); MainWindow.get_toolbox ().redraw ((int) stroke_width.x, (int) stroke_width.y, 70, 70); }); - Path.stroke_width = stroke_width.get_value (); + CanvasSettings.stroke_width = stroke_width.get_value (); // adjust precision string precision_value = Preferences.get ("precision"); @@ -83,7 +83,7 @@ Tool show_all_line_handles = new Tool ("show_all_line_handles"); show_all_line_handles.select_action.connect((self) => { - Path.show_all_line_handles = !Path.show_all_line_handles; + CanvasSettings.show_all_line_handles = !CanvasSettings.show_all_line_handles; Glyph g = MainWindow.get_current_glyph (); g.redraw_area (0, 0, g.allocation.width, g.allocation.height); }); @@ -91,11 +91,11 @@ Tool fill_open_path = new Tool ("fill_open_path"); fill_open_path.select_action.connect((self) => { - Path.fill_open_path = true; + CanvasSettings.fill_open_path = true; }); fill_open_path.deselect_action.connect((self) => { - Path.fill_open_path = false; + CanvasSettings.fill_open_path = false; }); tools.add (new SettingsItem (fill_open_path, t_("Fill open paths.")));
--- a/libbirdfont/SpinButton.vala +++ b/libbirdfont/SpinButton.vala @@ -44,6 +44,12 @@ /** Lock the button to a fixed value. */ public bool locked = false; + + static Gee.ArrayList<Text> digits; + static double text_height = 14; + static Text period; + static Text comma; + static Text minus; public SpinButton (string? name = null, string tip = "") { base (null , tip); @@ -51,7 +57,7 @@ if (name != null) { base.name = (!) name; } - + set_icon ("spin_button"); panel_press_action.connect ((selected, button, tx, ty) => { @@ -144,6 +150,23 @@ decrease (); return true; }); + + if (is_null (digits)) { + add_digits (); + } + } + + void add_digits () { + digits = new Gee.ArrayList<Text> (); + + for (int i = 0; i < 10; i++) { + Text digit = new Text (@"$i", text_height); + digits.add (digit); + } + + period = new Text (".", text_height); + comma = new Text (",", text_height); + minus = new Text ("-", text_height); } public void show_icon (bool i) { @@ -451,18 +474,31 @@ } return v; + } + + Text get_glyph (unichar character) { + Text text; + + if ('0' <= character <= '9') { + int digit_index = int.parse ((!) character.to_string ()); + text = digits.get (digit_index); + } else if (character == '.') { + text = period; + } else if (character == ',') { + text = comma; + } else if (character == '-') { + text = minus; + } else { + text = new Text ((!) character.to_string (), text_height); + } + + return text; } public override void draw_tool (Context cr, double px, double py) { - double scale = Toolbox.get_scale (); - double text_height = 14 * scale; - string display_value = get_short_display_value (); - Text text = new Text (display_value, text_height); double x = x - px; double y = y - py; - - double text_x = x + (w - text.get_sidebearing_extent ()) / 2 + 1; - double text_y = y + (h - text_height) / 2; + string display_value = get_short_display_value (); if (!show_icon_tool_icon || waiting_for_icon_switch) { if (is_selected ()) { @@ -481,18 +517,51 @@ base.draw_tool (cr, px, py); if (!show_icon_tool_icon || waiting_for_icon_switch) { - if (is_selected ()) { - Theme.text_color (text, "Selected Tool Foreground"); - } else { - Theme.text_color (text, "Tool Foreground"); + unichar digit; + int index; + Text text; + double extent = 0; + double decender = 0; + double carret = 0; + double total_extent = 0; + double x_offset; + + index = 0; + while (display_value.get_next_char (ref index, out digit)) { + text = get_glyph (digit); + total_extent += text.get_sidebearing_extent (); } - text.widget_x = text_x; - text.widget_y = text_y + text.get_decender (); - text.draw (cr); + x_offset = (w - total_extent) / 2 + 1; + + index = 0; + while (display_value.get_next_char (ref index, out digit)) { + text = get_glyph (digit); + extent = text.get_sidebearing_extent (); + + if (decender < text.get_decender ()) { + decender = text.get_decender (); + } + + if (is_selected ()) { + Theme.text_color (text, "Selected Tool Foreground"); + } else { + Theme.text_color (text, "Tool Foreground"); + } + + double text_x = x + carret + x_offset;; + double text_y = y + (h - text_height) / 2; + + text.widget_x = text_x; + text.widget_y = text_y + decender; + text.draw (cr); + + carret += extent; + + } } } } }
diff --git libbirdfont/Stop.vala(deleted)
--- a/libbirdfont/Stop.vala +++ /dev/null @@ -1,36 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - 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 3 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. - */ - - using Cairo; - using Math; - - namespace BirdFont { - - public class Stop : GLib.Object { - public Color color = Color.black (); - public double offset = 0; - - public Stop () { - } - - public Stop copy () { - Stop s = new Stop (); - s.color = color.copy (); - s.offset = offset; - return s; - } - } - - }
--- a/libbirdfont/StrokeTool.vala +++ b/libbirdfont/StrokeTool.vala @@ -14,14 +14,9 @@ using Cairo; using Math; + using SvgBird; namespace BirdFont { - - public enum LineCap { - BUTT, - SQUARE, - ROUND - } public class StrokeTool : GLib.Object { @@ -31,7 +26,7 @@ public static bool show_stroke_tools = false; public static bool convert_stroke = false; - public static LineCap line_cap = LineCap.BUTT; + public static SvgBird.LineCap line_cap = SvgBird.LineCap.BUTT; StrokeTask task; @@ -51,22 +46,27 @@ convert_stroke = true; g.store_undo_state (); - foreach (Path p in g.active_paths) { - if (p.stroke > 0) { - paths.append (p.get_completed_stroke ()); + foreach (SvgBird.Object o in g.active_paths) { + if (o is PathObject) { + PathObject path = (PathObject) o; + Path p = path.get_path (); + + if (p.stroke > 0) { + paths.append (p.get_completed_stroke ()); + } } } if (paths.paths.size > 0) { - foreach (Path p in g.active_paths) { - g.layers.remove_path (p); + foreach (SvgBird.Object o in g.active_paths) { + g.layers.remove (o); } g.active_paths.clear (); foreach (Path np in paths.paths) { g.add_path (np); - g.active_paths.add (np); + g.active_paths.add (new PathObject.for_path (np)); } PenTool.update_orientation (); @@ -134,11 +134,15 @@ g.store_undo_state (); - foreach (Path p in g.active_paths) { - if (p.stroke == 0) { - o.add (p); - } else { - o.append (p.get_completed_stroke ()); + foreach (SvgBird.Object object in g.active_paths) { + if (object is PathObject) { + Path p = ((PathObject) object).get_path (); + + if (p.stroke == 0) { + o.add (p); + } else { + o.append (p.get_completed_stroke ()); + } } } @@ -217,8 +221,8 @@ return; } - foreach (Path p in g.active_paths) { - g.delete_path (p); + foreach (SvgBird.Object object in g.active_paths) { + g.layers.remove (object); } g.clear_active_paths (); @@ -226,8 +230,9 @@ remove_merged_curve_parts (new_paths); foreach (Path p in new_paths.paths) { - g.add_path (p); - g.add_active_path (null, p); + PathObject path = new PathObject.for_path (p); + g.add_object (path); + g.add_active_object (path); } PenTool.update_orientation (); @@ -1141,9 +1146,9 @@ } void add_line_cap (Path path, Path stroke1, Path stroke2, bool last_cap) { - if (path.line_cap == LineCap.SQUARE) { + if (path.line_cap == SvgBird.LineCap.SQUARE) { add_square_cap (path, stroke1, stroke2, last_cap); - } else if (path.line_cap == LineCap.ROUND) { + } else if (path.line_cap == SvgBird.LineCap.ROUND) { add_round_cap (path, stroke1, stroke2, last_cap); } }
--- a/libbirdfont/Svg.vala +++ b/libbirdfont/Svg.vala @@ -15,6 +15,8 @@ using Cairo; namespace BirdFont { + + // FIXME: substrings public class Svg { @@ -81,13 +83,13 @@ } if (!p.is_open ()) { - m = p.points.get (0); + m = p.get_first_point (); add_abs_next ((!) n, m, svg, g, do_glyph); close_path (svg); } } - private static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool do_glyph) { + public static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg, Glyph g, bool do_glyph) { if (start.right_handle.type == PointType.LINE_QUADRATIC) { add_abs_line_to (start, end, svg, g, do_glyph); } else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_CUBIC) { @@ -118,7 +120,7 @@ } } - private static void close_path (StringBuilder svg) { + public static void close_path (StringBuilder svg) { svg.append ("z"); }
diff --git libbirdfont/SvgArc.vala(deleted)
--- a/libbirdfont/SvgArc.vala +++ /dev/null @@ -1,182 +1,1 @@ - /* - * BirdFont code from SVG Salamander - * - * Copyright (c) 2004, Mark McKay - * Copyright (c) 2014, Johan Mattsson - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the following - * disclaimer. - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * - * Mark McKay can be contacted at mark@kitfox.com. Salamander and other - * projects can be found at http://www.kitfox.com - * - * Created on January 26, 2004, 8:40 PM - * Adapded to BirdFont on Juli 2, 2014, 5:01 PM - */ - - using Math; - - namespace BirdFont { - - /** Convert an SVG arc instruction to a Beziér path. */ - static void add_arc_points (BezierPoints[] bezier_points, ref int bi, double x0, double y0, double rx, double ry, double angle, bool largeArcFlag, bool sweepFlag, double x, double y) { - - // - // Elliptical arc implementation based on the SVG specification notes - // - - double dx2, dy2, cosAngle, sinAngle; - double x1, y1, Prx, Pry, Px1, Py1, radiiCheck; - double sign, sq, coef, cx1, cy1; - double sx2, sy2, cx, cy; - double ux, uy, vx, vy, p, n; - double angleStart, angleExtent; - double s, step, theta; - - // Compute the half distance between the current and the final point - dx2 = (x0 - x) / 2.0; - dy2 = (y0 - y) / 2.0; - - // Convert angle from degrees to radians - angle = 2 * PI * ((angle % 360.0) / 360.0); - - cosAngle = cos (angle); - sinAngle = sin (angle); - - // - // Step 1 : Compute (x1, y1) - // - x1 = cosAngle * dx2 + sinAngle * dy2; - y1 = -sinAngle * dx2 + cosAngle * dy2; - - // Ensure radii are large enough - rx = fabs(rx); - ry = fabs(ry); - Prx = rx * rx; - Pry = ry * ry; - Px1 = x1 * x1; - Py1 = y1 * y1; - - - // Check that radii are large enough - radiiCheck = Px1 / Prx + Py1 / Pry; - - if (radiiCheck > 1) { - rx = sqrt (radiiCheck) * rx; - ry = sqrt (radiiCheck) * ry; - Prx = rx * rx; - Pry = ry * ry; - } - - // - // Step 2 : Compute (cx1, cy1) - // - sign = (largeArcFlag == sweepFlag) ? -1 : 1; - sq = ((Prx * Pry) - (Prx * Py1) - (Pry * Px1)) / ((Prx * Py1) + (Pry * Px1)); - sq = (sq < 0) ? 0 : sq; - coef = (sign * Math.sqrt(sq)); - cx1 = coef * ((rx * y1) / ry); - cy1 = coef * -((ry * x1) / rx); - - // - // Step 3 : Compute (cx, cy) from (cx1, cy1) - // - - sx2 = (x0 + x) / 2.0; - sy2 = (y0 + y) / 2.0; - cx = sx2 - (cosAngle * cx1 - sinAngle * cy1); - cy = sy2 - (sinAngle * cx1 + cosAngle * cy1); - - // - // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle) - // - - ux = (x1 - cx1) / rx; - uy = (y1 - cy1) / ry; - vx = (-x1 - cx1) / rx; - vy = (-y1 - cy1) / ry; - - // Compute the angle start - n = sqrt((ux * ux) + (uy * uy)); - p = ux; // (1 * ux) + (0 * uy) - sign = (uy < 0) ? -1d : 1d; - angleStart = sign * acos(p / n); - - // Compute the angle extent - n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); - p = ux * vx + uy * vy; - sign = (ux * vy - uy * vx < 0) ? -1d : 1d; - angleExtent = sign * Math.acos(p / n); - - if(!sweepFlag && angleExtent > 0) { - angleExtent -= 2 *PI; - } else if (sweepFlag && angleExtent < 0) { - angleExtent += 2 *PI; - } - angleExtent %= 2 * PI; - angleStart %= 2 * PI; - - angleExtent *= -1; - angleStart *= -1; - - // Approximate the path with Beziér points - s = (angleExtent > 0) ? 1 : -1; - step = fabs (angleExtent) / (2 * fabs (angleExtent)); - - theta = PI - angleStart - angleExtent; - - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'a'; - - bezier_points[bi].x0 = cx + rx * cos (theta); - bezier_points[bi].y0 = cy + ry * sin (theta); - - bi++; - - for (double a = 0; a < fabs (angleExtent); a += step) { - theta = PI - angleStart - angleExtent + s * a; - - return_if_fail (0 <= bi < bezier_points.length); - - bezier_points[bi].type = 'S'; - bezier_points[bi].svg_type = 'a'; - - bezier_points[bi].x0 = cx + rx * cos (theta); - bezier_points[bi].y0 = cy + ry * sin (theta); - - bezier_points[bi].x1 = cx + rx * cos (theta + 1 * step / 4); - bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 4); - - bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 4); - bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 4); - - bi++; - } - } - - } -
--- a/libbirdfont/SvgFont.vala +++ b/libbirdfont/SvgFont.vala @@ -13,6 +13,7 @@ */ using B; + using SvgBird; namespace BirdFont { @@ -253,11 +254,6 @@ glyph.right_limit = glyph.left_limit + advance * units; // FIXME: add svg font ligatures - /* - if (ligature != "") { - glyph.set_ligature_substitution (ligature); - } - */ glyph_collection = new GlyphCollection (unicode_value, glyph_name); glyph_collection.insert_glyph (glyph, true);
--- a/libbirdfont/SvgFontFormatWriter.vala +++ b/libbirdfont/SvgFontFormatWriter.vala @@ -14,7 +14,7 @@ namespace BirdFont { - class SvgFontFormatWriter : Object { + class SvgFontFormatWriter : GLib.Object { DataOutputStream os;
--- a/libbirdfont/SvgParser.vala +++ b/libbirdfont/SvgParser.vala @@ -14,17 +14,17 @@ using B; using Math; + using SvgBird; + using Cairo; namespace BirdFont { - public enum SvgFormat { - NONE, - INKSCAPE, - ILLUSTRATOR + public enum SvgType { + COLOR, + REGULAR } public class SvgParser { - SvgFormat format = SvgFormat.ILLUSTRATOR; public SvgParser () { @@ -34,7 +34,7 @@ format = f; } - public static void import () { + public static void import (SvgType type) { FileChooser fc = new FileChooser (); fc.file_selected.connect ((p) => { string path; @@ -44,14 +44,55 @@ } path = (!) p; - import_svg (path); + + if (type == SvgType.REGULAR) { + import_svg (path); + } else if (type == SvgType.COLOR) { + Glyph glyph = MainWindow.get_current_glyph (); + import_color_svg (glyph, path); + } }); fc.add_extension ("svg"); MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD); } - public static void import_folder () { + public static void import_svg_color_data (string svg_data) { + Glyph glyph = MainWindow.get_current_glyph (); + EmbeddedSvg drawing = SvgParser.parse_embedded_svg_data (svg_data); + glyph.add_object (drawing); + + Font font = BirdFont.get_current_font (); + + drawing.x = glyph.left_limit; + drawing.y = font.top_position - font.base_line; + + drawing.update_boundaries_for_object (); + + glyph.clear_active_paths (); + glyph.add_active_object (drawing); + } + + public static void import_color_svg (Glyph glyph, string path) { + string xml_data; + + try { + FileUtils.get_contents (path, out xml_data); + EmbeddedSvg drawing = SvgParser.parse_embedded_svg_data (xml_data); + + glyph.add_object (drawing); + drawing.update_boundaries_for_object (); + + Font font = BirdFont.get_current_font (); + + drawing.x = glyph.left_limit; + drawing.y = font.top_limit - font.base_line; + } catch (GLib.Error e) { + warning (e.message); + } + } + + public static void import_folder (SvgType type) { FileChooser fc = new FileChooser (); fc.file_selected.connect ((p) => { string path; @@ -78,7 +119,7 @@ if (file_name.has_suffix (".svg")) { svg = get_child (svg_folder, file_name); - imported = import_svg_file (font, svg); + imported = import_svg_file (font, svg, type); if (!imported) { warning ("Can't import %s.", (!) svg.get_path ()); @@ -95,13 +136,17 @@ MainWindow.file_chooser (t_("Import"), fc, FileChooser.LOAD | FileChooser.DIRECTORY); } - public static void import_svg_data (string xml_data, SvgFormat format = SvgFormat.NONE) { + public static PathList import_svg_data (string xml_data) { + Glyph glyph = MainWindow.get_current_glyph (); + SvgParser parser = new SvgParser (); + return parser.import_svg_data_in_glyph (xml_data, glyph); + } + + public PathList import_svg_data_in_glyph (string xml_data, Glyph glyph) { PathList path_list = new PathList (); - Glyph glyph; string[] lines = xml_data.split ("\n"); bool has_format = false; SvgParser parser = new SvgParser (); - XmlParser xmlparser; foreach (string l in lines) { if (l.index_of ("Illustrator") > -1 || l.index_of ("illustrator") > -1) { @@ -113,36 +158,27 @@ parser.set_format (SvgFormat.INKSCAPE); has_format = true; } - } - - if (format != SvgFormat.NONE) { - parser.set_format (format); } // parse the file if (!has_format) { + parser.set_format (SvgFormat.ILLUSTRATOR); warn_if_test ("No format identifier found in SVG parser.\n"); } - xmlparser = new XmlParser (xml_data); - - if (!xmlparser.validate()) { - warning("Invalid XML in SVG parser."); - } - - path_list = parser.parse_svg_file (xmlparser.get_root_tag ()); + XmlTree xml_tree = new XmlTree (xml_data); + path_list = parser.parse_svg_file (xml_tree.get_root ()); - glyph = MainWindow.get_current_glyph (); foreach (Path p in path_list.paths) { - glyph.add_path (p); + PathObject path = new PathObject.for_path (p); + glyph.add_object (path); + glyph.add_active_object (path); // FIXME: groups + path.update_boundaries_for_object (); } - foreach (Path p in path_list.paths) { - glyph.add_active_path (null, p); // FIXME: groups - p.update_region_boundaries (); - } + glyph.close_path (); - glyph.close_path (); + return path_list; } public static string replace (string content, string start, string stop, string replacement) { @@ -171,15 +207,34 @@ import_svg_data (svg_data); } - private PathList parse_svg_file (Tag tag) { - Layer pl = new Layer (); + private PathList parse_svg_file (XmlElement tag) { + Layer layer = new Layer (); + return parse_svg_tag (tag, layer); + } - foreach (Tag t in tag) { + private PathList parse_svg_tag (XmlElement tag, Layer pl) { + double width = 0; + double height = 0; + + foreach (Attribute attribute in tag.get_attributes ()) { + if (attribute.get_name () == "width") { + width = parse_double (attribute.get_content ()); + } + if (attribute.get_name () == "height") { + height = parse_double (attribute.get_content ()); + } + } + + foreach (XmlElement t in tag) { if (t.get_name () == "g") { parse_layer (t, pl); } + if (t.get_name () == "svg") { + parse_svg_tag (t, pl); + } + if (t.get_name () == "switch") { parse_layer (t, pl); } @@ -206,13 +261,29 @@ if (t.get_name () == "line") { parse_line (t, pl); - } + } + + if (t.get_name () == "rect") { + parse_rect (t, pl); + } + } + + PathList paths = LayerUtils.get_all_paths (pl); + ViewBox? box = SvgFile.parse_view_box (tag); + + if (box != null) { + ViewBox view_box = (!) box; + Matrix view_box_matrix = view_box.get_matrix (width, height); + SvgTransform t = new SvgTransform.for_matrix (view_box_matrix); + SvgTransforms transforms = new SvgTransforms (); + transforms.add (t); + transform_paths (transforms, paths); } - return pl.get_all_paths (); + return paths; } - private void parse_layer (Tag tag, Layer pl) { + private void parse_layer (XmlElement tag, Layer pl) { Layer layer; bool hidden = false; @@ -232,7 +303,7 @@ return; } - foreach (Tag t in tag) { + foreach (XmlElement t in tag) { if (t.get_name () == "path") { parse_path (t, pl); } @@ -240,9 +311,15 @@ if (t.get_name () == "g") { layer = new Layer (); parse_layer (t, layer); - pl.subgroups.add (layer); + pl.objects.add (layer); } - + + if (t.get_name () == "svg") { + layer = new Layer (); + parse_svg_tag (t, layer); + pl.objects.add (layer); + } + if (t.get_name () == "polygon") { parse_polygon (t, pl); } @@ -267,56 +344,63 @@ parse_line (t, pl); } } - - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform (attr.get_content (), pl); - } - } + + PathList paths = LayerUtils.get_all_paths (pl); + SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); + transform_paths (transforms, paths); } - private void transform (string transform_functions, Layer layer) { - transform_paths (transform_functions, layer.paths); - transform_subgroups (transform_functions, layer); - } - - private void transform_subgroups (string transform_functions, Layer layer) { - foreach (Layer subgroup in layer.subgroups) { - transform (transform_functions, subgroup); + private void transform_paths (SvgTransforms transforms, PathList pl) { + foreach (Path p in pl.paths) { + transform_path (transforms, p); } } - - private void transform_paths (string transform_functions, PathList pl) { - string data = transform_functions.dup (); - string[] functions; + + private void transform_path (SvgTransforms transforms, Path path) { + Font font = BirdFont.get_current_font (); + Glyph glyph = MainWindow.get_current_glyph (); - // use only a single space as separator - while (data.index_of (" ") > -1) { - data = data.replace (" ", " "); + foreach (EditPoint ep in path.points) { + ep.tie_handles = false; + ep.reflective_point = false; } - return_if_fail (data.index_of (")") > -1); - - // add separator - data = data.replace (") ", "|"); - data = data.replace (")", "|"); - functions = data.split ("|"); - - for (int i = functions.length - 1; i >= 0; i--) { - if (functions[i].has_prefix ("translate")) { - translate (functions[i], pl); - } - - if (functions[i].has_prefix ("scale")) { - scale (functions[i], pl); - } + Matrix matrix = transforms.get_matrix (); - if (functions[i].has_prefix ("matrix")) { - matrix (functions[i], pl); - } + foreach (EditPoint ep in path.points) { + double x, y; - // TODO: rotate etc. + x = ep.independent_x - glyph.left_limit; + y = font.top_limit - ep.independent_y; + + matrix.transform_point (ref x, ref y); + + ep.independent_x = glyph.left_limit + x; + ep.independent_y = font.top_limit - y; + + x = ep.get_right_handle ().x - glyph.left_limit; + y = font.top_limit - ep.get_right_handle ().y; + + matrix.transform_point (ref x, ref y); + + ep.get_right_handle ().x = glyph.left_limit + x; + ep.get_right_handle ().y = font.top_limit - y; + + x = ep.get_left_handle ().x - glyph.left_limit; + y = font.top_limit - ep.get_left_handle ().y; + + matrix.transform_point (ref x, ref y); + + ep.get_left_handle ().x = glyph.left_limit + x; + ep.get_left_handle ().y = font.top_limit - y; } + + double stroke_x = path.stroke; + double stroke_y = path.stroke; + + matrix.transform_distance (ref stroke_x, ref stroke_y); + + path.stroke = stroke_x; } /** @param path a path in the cartesian coordinate system @@ -344,7 +428,7 @@ apply_matrix_on_handle (left, a, b, c, d, e, f); } - ep.independent_y = font.top_position - ep.independent_y; + ep.independent_y = font.top_limit - ep.independent_y; ep.independent_x -= glyph.left_limit; dx = a * ep.independent_x + c * ep.independent_y + e; @@ -353,7 +437,7 @@ ep.independent_x = dx; ep.independent_y = dy; - ep.independent_y = font.top_position - ep.independent_y; + ep.independent_y = font.top_limit - ep.independent_y; ep.independent_x += glyph.left_limit; } } @@ -366,7 +450,7 @@ Font font = BirdFont.get_current_font (); Glyph glyph = MainWindow.get_current_glyph (); - h.y = font.top_position - h.y; + h.y = font.top_limit - h.y; h.x -= glyph.left_limit; dx = a * h.x + c * h.y + e; @@ -375,7 +459,7 @@ h.x = dx; h.y = dy; - h.y = font.top_position - h.y; + h.y = font.top_limit - h.y; h.x += glyph.left_limit; } @@ -458,7 +542,7 @@ return param.strip(); } - private void parse_circle (Tag tag, Layer pl) { + private void parse_circle (XmlElement tag, Layer pl) { Path p; double x, y, r; Glyph g; @@ -491,7 +575,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag, null); if (hidden) { return; @@ -511,17 +595,14 @@ npl.add (p); - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), npl); - } - } + SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); + transform_paths (transforms, npl); - style.apply (npl); - pl.paths.append (npl); + npl.apply_style (style); + append_paths (pl, npl); } - private void parse_ellipse (Tag tag, Layer pl) { + private void parse_ellipse (XmlElement tag, Layer pl) { Path p; double x, y, rx, ry; Glyph g; @@ -559,7 +640,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag, null); if (hidden) { return; @@ -579,17 +660,14 @@ npl.add (p); - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), npl); - } - } + SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); + transform_paths (transforms, npl); - style.apply (npl); - pl.paths.append (npl); + npl.apply_style (style); + append_paths (pl, npl); } - private void parse_line (Tag tag, Layer pl) { + private void parse_line (XmlElement tag, Layer pl) { Path p; double x1, y1, x2, y2; BezierPoints[] bezier_points; @@ -625,7 +703,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag, null); if (hidden) { return; @@ -656,17 +734,14 @@ npl.add (p); - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), npl); - } - } + SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); + transform_paths (transforms, npl); - style.apply (npl); - pl.paths.append (npl); + npl.apply_style (style); + append_paths (pl, npl); } - private void parse_rect (Tag tag, Layer pl) { + private void parse_rect (XmlElement tag, Layer layer) { Path p; double x, y, x2, y2; BezierPoints[] bezier_points; @@ -703,7 +778,7 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag, null); if (hidden) { return; @@ -757,32 +832,32 @@ npl.add (p); // FIXME: right layer for other transforms - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), npl); - } - } + SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); + transform_paths (transforms, npl); - style.apply (npl); - pl.paths.append (npl); + npl.apply_style (style); + append_paths (layer, npl); } - private void parse_polygon (Tag tag, Layer pl) { + private void parse_polygon (XmlElement tag, Layer layer) { PathList path_list = get_polyline (tag); foreach (Path p in path_list.paths) { p.close (); } - pl.paths.append (path_list); + append_paths (layer, path_list); } + static void append_paths (Layer layer, PathList pl) { + LayerUtils.append_paths (layer, pl); + } - private void parse_polyline (Tag tag, Layer pl) { - pl.paths.append (get_polyline (tag)); + private void parse_polyline (XmlElement tag, Layer layer) { + append_paths (layer, get_polyline (tag)); } - private PathList get_polyline (Tag tag) { + private PathList get_polyline (XmlElement tag) { Path p = new Path (); bool hidden = false; PathList path_list = new PathList (); @@ -798,25 +873,22 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag, null); if (hidden) { return path_list; } path_list.add (p); - style.apply (path_list); + path_list.apply_style (style); - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), path_list); - } - } + SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); + transform_paths (transforms, path_list); return path_list; } - private void parse_path (Tag tag, Layer pl) { + private void parse_path (XmlElement tag, Layer layer) { Glyph glyph = MainWindow.get_current_glyph (); PathList path_list = new PathList (); SvgStyle style = new SvgStyle (); @@ -838,50 +910,57 @@ } } - style = SvgStyle.parse (tag.get_attributes ()); + style = SvgStyle.parse (null, style, tag, null); if (hidden) { return; } - - pl.paths.append (path_list); - style.apply (path_list); + + foreach (Path path in path_list.paths) { + LayerUtils.add_path (layer, path); + } + + path_list.apply_style (style); // assume the even odd rule is applied and convert the path // to a path using the non-zero rule int inside_count; bool inside; - foreach (Path p1 in pl.paths.paths) { - inside_count = 0; - - foreach (Path p2 in pl.paths.paths) { - if (p1 != p2) { - inside = true; + foreach (SvgBird.Object o1 in layer.objects) { + if (o1 is PathObject) { + Path p1 = ((PathObject) o1).get_path (); + inside_count = 0; + + foreach (SvgBird.Object o2 in layer.objects) { + if (o2 is PathObject) { + Path p2 = ((PathObject) o2).get_path (); - foreach (EditPoint ep in p1.points) { - if (!is_inside (ep, p2)) { - inside = false; + if (p1 != p2) { + inside = true; + + foreach (EditPoint ep in p1.points) { + if (!is_inside (ep, p2)) { + inside = false; + } + } + + if (inside) { + inside_count++; + } } - } - - if (inside) { - inside_count++; } } - } - - if (inside_count % 2 == 0) { - p1.force_direction (Direction.CLOCKWISE); - } else { - p1.force_direction (Direction.COUNTER_CLOCKWISE); + + if (inside_count % 2 == 0) { + p1.force_direction (Direction.CLOCKWISE); + } else { + p1.force_direction (Direction.COUNTER_CLOCKWISE); + } } } - foreach (Attribute attr in tag.get_attributes ()) { - if (attr.get_name () == "transform") { - transform_paths (attr.get_content (), path_list); - } - } + SvgTransforms transforms = SvgFile.get_transform (tag.get_attributes ()); + transform_paths (transforms, path_list); } public static void create_lines_for_segment (Path path, EditPoint start, EditPoint end, double tolerance) { @@ -976,49 +1055,6 @@ } return inside; - } - - /** Add space as separator to svg data. - * @param d svg data - */ - static string add_separators (string d) { - string data = d; - - data = data.replace (",", " "); - data = data.replace ("a", " a "); - data = data.replace ("A", " A "); - data = data.replace ("m", " m "); - data = data.replace ("M", " M "); - data = data.replace ("h", " h "); - data = data.replace ("H", " H "); - data = data.replace ("v", " v "); - data = data.replace ("V", " V "); - data = data.replace ("l", " l "); - data = data.replace ("L", " L "); - data = data.replace ("q", " q "); - data = data.replace ("Q", " Q "); - data = data.replace ("c", " c "); - data = data.replace ("C", " C "); - data = data.replace ("t", " t "); - data = data.replace ("T", " T "); - data = data.replace ("s", " s "); - data = data.replace ("S", " S "); - data = data.replace ("zM", " z M "); - data = data.replace ("zm", " z m "); - data = data.replace ("z", " z "); - data = data.replace ("Z", " Z "); - data = data.replace ("-", " -"); - data = data.replace ("e -", "e-"); // minus can be either separator or a negative exponent - data = data.replace ("\t", " "); - data = data.replace ("\r\n", " "); - data = data.replace ("\n", " "); - - // use only a single space as separator - while (data.index_of (" ") > -1) { - data = data.replace (" ", " "); - } - - return data; } public void add_path_to_glyph (string d, Glyph g, bool svg_glyph = false, double units = 1) { @@ -1028,6 +1064,36 @@ } } + public void get_bezier_points (string svg_data, out BezierPoints[] instructions, out int points, bool svg_glyph) { + SvgFile.get_bezier_points (svg_data, out instructions, out points, svg_glyph); + Gee.ArrayList<BezierPoints> bezier_points = new Gee.ArrayList<BezierPoints> (); + BezierPoints[] arc_data = new BezierPoints[8]; + + for (int i = 0; i < points; i++) { + if (instructions[i].type == 'A') { + int arc_index = 0; + + add_arc_points (arc_data, ref arc_index, + instructions[i].x0, instructions[i].y0, + instructions[i].rx, instructions[i].ry, + instructions[i].rotation, + instructions[i].large_arc, + instructions[i].sweep, + instructions[i].x1, instructions[i].y1); + + for (int j = 0; j < arc_index; j++) { + bezier_points.add (instructions[j]); + } + } + + bezier_points.add (instructions[i]); + } + + instructions = new BezierPoints[bezier_points.size]; + for (int i = 0; i < bezier_points.size; i++) { + instructions[i] = bezier_points.get (i); + } + } /** * @param d svg data * @param glyph use lines from this glyph but don't add the generated paths @@ -1036,559 +1102,110 @@ * @return the new paths */ public PathList parse_svg_data (string d, Glyph glyph, bool svg_glyph = false, double units = 1) { - double px = 0; - double py = 0; - double px2 = 0; - double py2 = 0; - double cx = 0; - double cy = 0; - string data; Font font; PathList path_list = new PathList (); BezierPoints[] bezier_points; - string[] c; - double arc_rx, arc_ry; - double arc_rotation; - int large_arc; - int arc_sweep; - double arc_dest_x, arc_dest_y; + int points; font = BirdFont.get_current_font (); - - data = add_separators (d); - c = data.split (" "); - bezier_points = new BezierPoints[8 * c.length + 1]; // the arc instruction can use up to eight points - - for (int i = 0; i < 2 * c.length + 1; i++) { - bezier_points[i] = new BezierPoints (); + SvgFile.get_bezier_points (d, out bezier_points, out points, svg_glyph); + + if (points == 0) { + warning ("No points in path."); + return path_list; } - int bi = 0; + move_and_resize (bezier_points, points, svg_glyph, units, glyph); + path_list = create_svg_paths (d); + + // TODO: Find out if it is possible to tie handles. + return path_list; + } + + public PathList create_svg_paths (string path_data) { + Gee.ArrayList<Points> points_set = SvgFile.parse_points (path_data, format); + PathList path_list = new PathList (); - // parse path - int i = -1; - while (++i < c.length && bi < bezier_points.length) { - if (c[i] == "m") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'M'; - bezier_points[bi].svg_type = 'm'; - - px += parse_double (c[++i]); - - if (svg_glyph) { - py += parse_double (c[++i]); - } else { - py += -parse_double (c[++i]); - } - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (c[i] == "M") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'M'; - bezier_points[bi].svg_type = 'M'; - - px = parse_double (c[++i]); - - if (svg_glyph) { - py = parse_double (c[++i]); - } else { - py = -parse_double (c[++i]); - } - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (c[i] == "h") { - while (i + 1 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'h'; - - px += parse_double (c[++i]); + foreach (Points p in points_set) { + Path path = new Path (); + + PointValue* points = p.point_data.data; + EditPoint next = new EditPoint (); + EditPointHandle handle; - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (i + 1 < c.length && c[i] == "H") { - while (is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'H'; + if (p.size == 0) { + warning ("No points in path."); + return path_list; + } + + if (p.size % 8 != 0) { + warning ("Points not padded."); + return path_list; + } + + for (int i = 0; i < p.size; i += 8) { + + switch (points[i].type) { + case POINT_ARC: + // FIXME: + /* + draw_arc (cr, , points[i + 2].value, + points[i + 3].value, points[i + 4].value, + points[i + 5].value, points[i + 6].value, + points[i + 7].value); + */ + break; + case POINT_CUBIC: + handle = next.get_right_handle (); + handle.x = points[i + 1].value; + handle.y = -points[i + 2].value; + handle.type = PointType.CUBIC; - px = parse_double (c[++i]); - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (c[i] == "v") { - while (i + 1 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'v'; - - if (svg_glyph) { - py = py + parse_double (c[++i]); - } else { - py = py - parse_double (c[++i]); - } - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (i + 1 < c.length && c[i] == "V") { - while (is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'V'; - - if (svg_glyph) { - py = parse_double (c[++i]); - } else { - py = -parse_double (c[++i]); - } - - bezier_points[bi].x0 = px; - bezier_points[bi].y0 = py; - bi++; - } - } else if (c[i] == "l") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'l'; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } + next = new EditPoint (points[i + 5].value, -points[i + 6].value, PointType.CUBIC); + path.add_point (next); - px = cx; - py = cy; + handle = next.get_left_handle (); + handle.x = points[i + 3].value; + handle.y = -points[i + 4].value; + handle.type = PointType.CUBIC; - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - bi++; - } - } else if (c[i] == "L") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'L'; - bezier_points[bi].svg_type = 'L'; - - cx = parse_double (c[++i]); + break; + case POINT_LINE: + handle = next.get_right_handle (); + handle.type = PointType.LINE_CUBIC; - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } + next = new EditPoint (points[i + 1].value, -points[i + 2].value, PointType.CUBIC); + path.add_point (next); - px = cx; - py = cy; + handle = next.get_left_handle (); + handle.type = PointType.LINE_CUBIC; - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - bi++; - } - } else if (c[i] == "c") { - while (i + 6 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'C'; - bezier_points[bi].svg_type = 'C'; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - px2 = cx; - py2 = cy; - - bezier_points[bi].x1 = px2; - bezier_points[bi].y1 = py2; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py + -parse_double (c[++i]); - } - - bezier_points[bi].x2 = cx; - bezier_points[bi].y2 = cy; - - px = cx; - py = cy; - - bi++; + break; } - } else if (c[i] == "C") { - while (i + 6 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'C'; - bezier_points[bi].svg_type = 'C'; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - px2 = cx; - py2 = cy; - - bezier_points[bi].x1 = cx; - bezier_points[bi].y1 = cy; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - bezier_points[bi].x2 = cx; - bezier_points[bi].y2 = cy; - - px = cx; - py = cy; - - bi++; - } - } else if (c[i] == "q") { - while (i + 4 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'Q'; - bezier_points[bi].svg_type = 'q'; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - px2 = cx; - py2 = cy; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - bezier_points[bi].x1 = cx; - bezier_points[bi].y1 = cy; - - px = cx; - py = cy; - - bi++; - } - } else if (c[i] == "Q") { + } + + if (p.closed) { + path.close (); + } - while (i + 4 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'Q'; - bezier_points[bi].svg_type = 'Q'; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - px2 = cx; - py2 = cy; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - px = cx; - py = cy; + Font font = BirdFont.get_current_font (); + Glyph glyph = MainWindow.get_current_glyph (); + + foreach (EditPoint e in path.points) { + e.independent_x += glyph.left_limit; + e.independent_y += font.top_limit; + e.get_right_handle ().independent_x += glyph.left_limit; + e.get_right_handle ().independent_y += font.top_limit; + e.get_left_handle ().independent_x += glyph.left_limit; + e.get_left_handle ().independent_y += font.top_limit; + } + + path.recalculate_linear_handles (); - bezier_points[bi].x1 = cx; - bezier_points[bi].y1 = cy; - - bi++; - } - } else if (c[i] == "t") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'Q'; - bezier_points[bi].svg_type = 't'; - - // the first point is the reflection - cx = 2 * px - px2; - cy = 2 * py - py2; // if (svg_glyph) ? - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - px2 = cx; - py2 = cy; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - px = cx; - py = cy; - - bezier_points[bi].x1 = px; - bezier_points[bi].y1 = py; - - bi++; - } - } else if (c[i] == "T") { - while (i + 2 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'Q'; - bezier_points[bi].svg_type = 'T'; - - // the reflection - cx = 2 * px - px2; - cy = 2 * py - py2; // if (svg_glyph) ? - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - px2 = cx; - py2 = cy; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - px = cx; - py = cy; - - bezier_points[bi].x1 = px; - bezier_points[bi].y1 = py; - - bi++; - } - } else if (c[i] == "s") { - while (i + 4 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'C'; - bezier_points[bi].svg_type = 's'; - - // the first point is the reflection - cx = 2 * px - px2; - cy = 2 * py - py2; // if (svg_glyph) ? - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - px2 = cx; - py2 = cy; - - bezier_points[bi].x1 = px2; - bezier_points[bi].y1 = py2; - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - bezier_points[bi].x2 = cx; - bezier_points[bi].y2 = cy; - - px = cx; - py = cy; - - bi++; - } - } else if (c[i] == "S") { - while (i + 4 < c.length && is_point (c[i + 1])) { - bezier_points[bi].type = 'C'; - bezier_points[bi].svg_type = 'S'; - - // the reflection - cx = 2 * px - px2; - cy = 2 * py - py2; // if (svg_glyph) ? - - bezier_points[bi].x0 = cx; - bezier_points[bi].y0 = cy; - - // the other two are regular cubic points - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - px2 = cx; - py2 = cy; - - bezier_points[bi].x1 = px2; - bezier_points[bi].y1 = py2; - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - bezier_points[bi].x2 = cx; - bezier_points[bi].y2 = cy; - - px = cx; - py = cy; - - bi++; - } - } else if (c[i] == "a") { - while (i + 7 < c.length && is_point (c[i + 1])) { - arc_rx = parse_double (c[++i]); - arc_ry = parse_double (c[++i]); - - arc_rotation = parse_double (c[++i]); - large_arc = parse_int (c[++i]); - arc_sweep = parse_int (c[++i]); - - cx = px + parse_double (c[++i]); - - if (svg_glyph) { - cy = py + parse_double (c[++i]); - } else { - cy = py - parse_double (c[++i]); - } - - arc_dest_x = cx; - arc_dest_y = cy; - - add_arc_points (bezier_points, ref bi, px, py, arc_rx, arc_ry, arc_rotation, large_arc == 1, arc_sweep == 1, cx, cy); - - px = cx; - py = cy; - - - } - } else if (i + 7 < c.length && c[i] == "A") { - while (is_point (c[i + 1])) { - arc_rx = parse_double (c[++i]); - arc_ry = parse_double (c[++i]); - - arc_rotation = parse_double (c[++i]); - large_arc = parse_int (c[++i]); - arc_sweep = parse_int (c[++i]); - - cx = parse_double (c[++i]); - - if (svg_glyph) { - cy = parse_double (c[++i]); - } else { - cy = -parse_double (c[++i]); - } - - arc_dest_x = cx; - arc_dest_y = cy; - - add_arc_points (bezier_points, ref bi, px, py, arc_rx, arc_ry, arc_rotation, large_arc == 1, arc_sweep == 1, cx, cy); - - px = cx; - py = cy; - - - } - } else if (c[i] == "z") { - bezier_points[bi].type = 'z'; - bezier_points[bi].svg_type = 'z'; - - bi++; - } else if (c[i] == "Z") { - bezier_points[bi].type = 'z'; - bezier_points[bi].svg_type = 'z'; - - bi++; - } else if (c[i] == "") { - } else if (c[i] == " ") { - } else { - warning (@"Unknown instruction: $(c[i])"); - } + path.remove_points_on_points (); + path_list.add (path); } - if (bi == 0) { - warning ("No points in path."); - return path_list; - } - - move_and_resize (bezier_points, bi, svg_glyph, units, glyph); - - if (format == SvgFormat.ILLUSTRATOR) { - path_list = create_paths_illustrator (bezier_points, bi); - } else { - path_list = create_paths_inkscape (bezier_points, bi); - } - - // TODO: Find out if it is possible to tie handles. return path_list; } @@ -1622,400 +1239,14 @@ } } } - - void find_last_handle (int start_index, BezierPoints[] b, int num_b, out double left_x, out double left_y, out PointType last_type) { - BezierPoints last = new BezierPoints (); - bool found = false; - - left_x = 0; - left_y = 0; - last_type = PointType.NONE; - - return_if_fail (b.length != 0); - return_if_fail (b[0].type != 'z'); - return_if_fail (num_b < b.length); - if (num_b == 2) { - left_x = b[0].x0 + (b[1].x0 - b[0].x0) / 3.0; - left_y = b[0].y0 + (b[1].y0 - b[0].y0) / 3.0; - last_type = PointType.LINE_CUBIC; - return; - } - - for (int i = start_index; i < num_b; i++) { - switch (b[i].type) { - case 'Q': - break; - case 'C': - break; - case 'z': - found = true; - break; - default: - break; - } - - if (found || i + 1 == num_b) { - - return_if_fail (i >= 1); - - if (b[i - 1].type == 'Q') { - return_if_fail (i >= 1); - left_x = b[i - 1].x0; - left_y = b[i - 1].y0; - last_type = PointType.QUADRATIC; - } else if (b[i - 1].type == 'C') { - return_if_fail (i >= 1); - left_x = b[i - 1].x1; - left_y = b[i - 1].y1; - last_type = PointType.CUBIC; - } else if (b[i - 1].type == 'S') { - return_if_fail (i >= 1); - left_x = b[i - 1].x1; - left_y = b[i - 1].y1; - last_type = PointType.CUBIC; - } else if (b[i - 1].type == 'L' || last.type == 'M') { - return_if_fail (i >= 2); // FIXME: -2 can be C or L - left_x = b[i - 2].x0 + (b[i - 1].x0 - b[i - 2].x0) / 3.0; - left_y = b[i - 2].y0 + (b[i - 1].y0 - b[i - 2].y0) / 3.0; - last_type = PointType.LINE_CUBIC; - } else { - warning (@"Unexpected type. $(b[i - 1])\n"); - } - return; - } - - last = b[i]; - } - - warning ("Last point not found."); - } - - PathList create_paths_inkscape (BezierPoints[] b, int num_b) { - double last_x; - double last_y; - PointType last_type; - Path path; - PathList path_list = new PathList (); - EditPoint ep = new EditPoint (); - Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> (); - - path = new Path (); - - if (num_b == 0) { - warning ("No SVG data"); - return path_list; - } - - if (b[0].type != 'M') { - warning ("Path must begin with M or m."); - return path_list; - } - - find_last_handle (0, b, num_b, out last_x, out last_y, out last_type); - - for (int i = 0; i < num_b; i++) { - if (b[i].type == '\0') { - warning ("Parser error."); - return path_list; - } - - if (b[i].type == 'z') { - path.close (); - path.create_list (); - path.recalculate_linear_handles (); - path_list.add (path); - path = new Path (); - - if (i + 1 >= num_b) { - break; - } else { - find_last_handle (i + 1, b, num_b, out last_x, out last_y, out last_type); - } - } - - if (i >= num_b) { - break; - } - - if (b[i].type == 'M') { - ep = path.add (b[i].x0, b[i].y0); - ep.set_point_type (PointType.CUBIC); - - ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); - - if (i == 0 || (b[i - 1].type == 'z')) { - ep.get_left_handle ().set_point_type (last_type); - ep.get_left_handle ().move_to_coordinate (last_x, last_y); - } else { - if (b[i - 1].type == 'C' || b[i - 1].type == 'S') { - ep.get_left_handle ().set_point_type (PointType.CUBIC); - ep.get_left_handle ().move_to_coordinate (b[i + 1].x1, b[i + 1].y1); - } - - if (b[i + 1].type == 'C' || b[i - 1].type == 'S') { - ep.get_right_handle ().set_point_type (PointType.CUBIC); - ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); - } else if (b[i + 1].type == 'L' || b[i + 1].type == 'M') { - ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - } - } - } - - if (b[i].type == 'L') { - return_val_if_fail (i != 0, path_list); - - ep = path.add (b[i].x0, b[i].y0); - ep.set_point_type (PointType.CUBIC); - ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); - - if (b[i + 1].type == 'L' || b[i + 1].type == 'M' || b[i + 1].type == 'z') { - ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - } - - if (b[i -1].type == 'L' || b[i - 1].type == 'M') { - ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); - } - } - - if (b[i].type == 'Q') { - return_val_if_fail (i != 0, path_list); - - ep.set_point_type (PointType.QUADRATIC); - - ep.get_right_handle ().set_point_type (PointType.QUADRATIC); - ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); - - if (b[i + 1].type != 'z') { - ep = path.add (b[i].x1, b[i].y1); - - ep.get_left_handle ().set_point_type (PointType.QUADRATIC); - ep.get_left_handle ().move_to_coordinate (b[i].x0, b[i].y0); - } - } - - if (b[i].type == 'C' || b[i].type == 'S') { - return_val_if_fail (i != 0, path_list); - - ep.set_point_type (PointType.CUBIC); - - ep.get_right_handle ().set_point_type (PointType.CUBIC); - ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); - - if (b[i].type == 'S') { - smooth_points.add (ep); - } - - if (b[i + 1].type != 'z') { - ep = path.add (b[i].x2, b[i].y2); - - ep.get_left_handle ().set_point_type (PointType.CUBIC); - ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1); - } - } - } - - foreach (EditPoint e in smooth_points) { - e.set_point_type (PointType.LINE_DOUBLE_CURVE); - e.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); - e.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE); - } - - foreach (EditPoint e in smooth_points) { - path.recalculate_linear_handles_for_point (e); - } - - for (int i = 0; i < 3; i++) { - foreach (EditPoint e in smooth_points) { - e.set_tie_handle (true); - e.process_tied_handle (); - } - } - - if (path.points.size > 0) { - path_list.add (path); - } - - foreach (Path p in path_list.paths) { - p.remove_points_on_points (); - } - - return path_list; - } - - PathList create_paths_illustrator (BezierPoints[] b, int num_b) { - Path path; - PathList path_list = new PathList (); - EditPoint ep; - bool first_point = true; - double first_left_x, first_left_y; - Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> (); - - if (num_b > b.length) { - warning ("num_b > b.length: $num_b > $(b.length)"); - return path_list; - } - - path = new Path (); - - if (num_b <= 1) { - warning ("No SVG data"); - return path_list; - } - - first_left_x = 0; - first_left_y = 0; - - ep = new EditPoint (); - - for (int i = 0; i < num_b; i++) { - if (b[i].type == '\0') { - warning ("Parser error."); - return path_list; - } else if (b[i].type == 'z') { - path.close (); - path.create_list (); - - int first_index = 1; - - for (int j = i - 1; j >= 1; j--) { - if (b[j].type == 'z') { - first_index = j + 1; // from z to M - } - } - - if (b[first_index].type == 'C' || b[first_index].type == 'S') { - return_val_if_fail (path.points.size != 0, path_list); - ep = path.points.get (path.points.size - 1); - - if (b[i - 1].type != 'L' ) { - ep.get_right_handle ().set_point_type (PointType.CUBIC); - ep.get_right_handle ().move_to_coordinate (b[first_index].x0, b[first_index].y0); - } - } else if (b[first_index].type == 'L') { - return_val_if_fail (path.points.size != 0, path_list); - ep = path.points.get (path.points.size - 1); - ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - path.recalculate_linear_handles_for_point (ep); - } else { - warning ("Unexpected type: %s", (!) b[first_index].type.to_string ()); - } - - path.recalculate_linear_handles (); - path_list.add (path); - - path = new Path (); - first_point = true; - } else if (b[i].type == 'L' || b[i].type == 'M') { - - if (first_point) { - first_left_x = b[i].x0; - first_left_y = b[i].y0; - } - - ep = path.add (b[i].x0, b[i].y0); - ep.set_point_type (PointType.CUBIC); // TODO: quadratic - ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - - ep.get_left_handle ().set_point_type (PointType.CUBIC); - ep.get_left_handle ().move_to_coordinate (b[i].x0 - 0.00001, b[i].y0 - 0.00001); - - if (b[i + 1].type == 'C' || b[i + 1].type == 'S') { - return_val_if_fail (i + 1 < num_b, path_list); - ep.get_right_handle ().set_point_type (PointType.CUBIC); - ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); - } - - first_point = false; - } else if (b[i].type == 'Q') { - warning ("Illustrator does not support quadratic control points."); - warning (@"$(b[i])\n"); - } else if (b[i].type == 'C' || b[i].type == 'S') { - - if (first_point) { - first_left_x = b[i].x0; - first_left_y = b[i].y0; - } - - ep = path.add (b[i].x2, b[i].y2); - ep.set_point_type (PointType.CUBIC); - - ep.get_right_handle ().set_point_type (PointType.CUBIC); - ep.get_left_handle ().set_point_type (PointType.CUBIC); - - ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1); - - if (b[i].type == 'S') { - smooth_points.add (ep); - } - - if (b[i + 1].type != 'z' && i != num_b - 1) { - ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); - } else { - ep.get_right_handle ().move_to_coordinate (first_left_x, first_left_y); - } - - first_point = false; - } else { - warning ("Unknown control point type."); - warning (@"$(b[i])\n"); - } - } - - foreach (EditPoint e in smooth_points) { - e.set_point_type (PointType.LINE_CUBIC); - e.get_right_handle ().set_point_type (PointType.LINE_CUBIC); - e.get_left_handle ().set_point_type (PointType.LINE_CUBIC); - } - - foreach (EditPoint e in smooth_points) { - path.recalculate_linear_handles_for_point (e); - } - - for (int i = 0; i < 3; i++) { - foreach (EditPoint e in smooth_points) { - e.set_tie_handle (true); - e.get_right_handle ().set_point_type (PointType.CUBIC); - e.get_left_handle ().set_point_type (PointType.CUBIC); - e.process_tied_handle (); - } - } - - if (path.points.size > 0) { - path_list.add (path); - } - - foreach (Path p in path_list.paths) { - p.remove_points_on_points (); - } - - return path_list; - } - - // TODO: implement a default svg parser - - static int parse_int (string? s) { - if (is_null (s)) { - warning ("null instead of string"); - return 0; - } - - if (!is_point ((!) s)) { - warning (@"Expecting an integer got: $((!) s)"); - return 0; - } - - return int.parse ((!) s); - } - - static double parse_double (string? s) { - if (is_null (s)) { + public static double parse_double (string? s) { + if (unlikely (is_null (s))) { warning ("Got null instead of expected string."); return 0; } - if (!is_point ((!) s)) { + if (unlikely (!is_point ((!) s))) { warning (@"Expecting a double got: $((!) s)"); return 0; } @@ -2036,7 +1267,7 @@ } Path parse_poly_data (string polygon_points) { - string data = add_separators (polygon_points); + string data = SvgFile.add_separators (polygon_points); string[] c = data.split (" "); Path path; BezierPoints[] bezier_points = new BezierPoints[c.length + 1]; @@ -2057,7 +1288,7 @@ } bezier_points[bi] = new BezierPoints (); - bezier_points[bi].type == 'L'; + bezier_points[bi].type = 'L'; bezier_points[bi].x0 = parse_double (c[i]); bezier_points[bi].y0 = -parse_double (c[i + 1]); bi++; @@ -2076,8 +1307,81 @@ path.recalculate_linear_handles (); return path; + } + + public static EmbeddedSvg parse_embedded_svg_file (string path) { + string xml_data; + + try { + FileUtils.get_contents (path, out xml_data); + return parse_embedded_svg_data (xml_data); + } catch (GLib.Error error) { + warning (error.message); + } + + SvgDrawing drawing = new SvgDrawing (); + return new EmbeddedSvg (drawing); + } + + public static EmbeddedSvg parse_embedded_svg_data (string xml_data) { + SvgDrawing drawing = new SvgDrawing (); + SvgFile svg_file = new SvgFile (); + drawing = svg_file.parse_svg_data (xml_data); + EmbeddedSvg svg = new EmbeddedSvg (drawing); + svg.svg_data = xml_data; + return svg; + } + + /** Convert an SVG arc instruction to a Beziér path. */ + public static void add_arc_points (BezierPoints[] bezier_points, ref int bi, + double x0, double y0, double rx, double ry, double angle, + bool largeArcFlag, bool sweepFlag, double x, double y) { + + double angleStart, angleExtent; + double s, step, theta; + double cx, cy; + + cx = 0; + cy = 0; + + // Approximate the path with Beziér points + SvgBird.get_arc_arguments (x0, y0, rx, ry, angle, largeArcFlag, sweepFlag, x, y, + out angleStart, out angleExtent, out cx, out cx); + + s = (angleExtent > 0) ? 1 : -1; + step = fabs (angleExtent) / (2 * fabs (angleExtent)); + + theta = PI - angleStart - angleExtent; + + bezier_points[bi].type = 'C'; + bezier_points[bi].svg_type = 'a'; + + bezier_points[bi].x0 = cx + rx * cos (theta); + bezier_points[bi].y0 = cy + ry * sin (theta); + + bi++; + + for (double a = 0; a < fabs (angleExtent); a += step) { + theta = PI - angleStart - angleExtent + s * a; + + return_if_fail (0 <= bi < bezier_points.length); + + bezier_points[bi].type = 'S'; + bezier_points[bi].svg_type = 'a'; + + bezier_points[bi].x0 = cx + rx * cos (theta); + bezier_points[bi].y0 = cy + ry * sin (theta); + + bezier_points[bi].x1 = cx + rx * cos (theta + 1 * step / 4); + bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 4); + + bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 4); + bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 4); + + bi++; + } } } }
diff --git libbirdfont/SvgStyle.vala(deleted)
--- a/libbirdfont/SvgStyle.vala +++ /dev/null @@ -1,125 +1,1 @@ - /* - Copyright (C) 2015 Johan Mattsson - - 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 3 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. - */ - - using B; - using Math; - - namespace BirdFont { - - public class SvgStyle { - - Gee.HashMap<string, string> style; - - public SvgStyle () { - style = new Gee.HashMap<string, string> (); - } - - public LineCap get_line_cap () { - string l; - - if (!style.has_key ("stroke-linecap")) { - return LineCap.BUTT; - } - - l = style.get ("stroke-linecap"); - - if (l == "round") { - return LineCap.ROUND; - } else if (l == "square") { - return LineCap.SQUARE; - } - - return LineCap.BUTT; - } - - public bool has_stroke () { - bool s = true; - - if (style.has_key ("stroke")) { - s = style.get ("stroke") != "none"; - } - - return get_stroke_width () > 0 && s; - } - - public double get_stroke_width () { - if (!style.has_key ("stroke-width")) { - return 0; - } - - return double.parse (style.get ("stroke-width")); - } - - - public static SvgStyle parse (Attributes attributes) { - SvgStyle s = new SvgStyle (); - - foreach (Attribute a in attributes) { - if (a.get_name () == "style") { - s.parse_key_value_pairs (a.get_content ()); - } - - if (a.get_name () == "stroke-width") { - s.style.set ("stroke-width", a.get_content ()); - } - - if (a.get_name () == "stroke") { - s.style.set ("stroke", a.get_content ()); - } - - if (a.get_name () == "fill") { - s.style.set ("fill", a.get_content ()); - } - } - - return s; - } - - void parse_key_value_pairs (string svg_style) { - string[] p = svg_style.split (";"); - string[] pair; - string k, v; - - foreach (string kv in p) { - pair = kv.split (":"); - - if (pair.length != 2) { - warning ("pair.length != 2"); - continue; - } - - k = pair[0]; - v = pair[1]; - - style.set (k, v); - } - } - - public void apply (PathList path_list) { - foreach (Path p in path_list.paths) { - if (has_stroke ()) { - p.stroke = get_stroke_width (); - } else { - p.stroke = 0; - } - - p.line_cap = get_line_cap (); - p.reset_stroke (); - p.update_region_boundaries (); - } - } - } - - }
--- a/libbirdfont/TabBar.vala +++ b/libbirdfont/TabBar.vala @@ -873,7 +873,7 @@ if (processing) { timer = new TimeoutSource (250); timer.set_callback (() => { - wheel_rotation += 0.008 * 2 * Math.PI; + wheel_rotation += 0.08 * 2 * Math.PI; if (wheel_rotation > 2 * Math.PI) { wheel_rotation -= 2 * Math.PI;
--- a/libbirdfont/TabContent.vala +++ b/libbirdfont/TabContent.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2014 2015 Johan Mattsson + Copyright (C) 2014 - 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as
--- a/libbirdfont/Task.vala +++ b/libbirdfont/Task.vala @@ -69,8 +69,6 @@ } task (); - - warning ("Task is done."); } public void* perform_task() {
--- a/libbirdfont/Test.vala +++ b/libbirdfont/Test.vala @@ -14,7 +14,7 @@ namespace BirdFont { - public class Test : Object { + public class Test : GLib.Object { public Callback callback; public string name; double time_stamp;
--- a/libbirdfont/TestCases.vala +++ b/libbirdfont/TestCases.vala @@ -13,7 +13,7 @@ */ using B; - + using SvgBird; namespace BirdFont { /** All the things we want to test listed is here. */ @@ -33,7 +33,6 @@ add (test_drawing, "Pen tool"); add (test_delete_points, "Delete edit points"); add (test_convert_to_quadratic_bezier_path, "Convert to quadratic path"); - add (test_over_path, "Over path"); add (test_export, "Export"); add (test_background_coordinates, "Background coordinates"); add (test_spin_button, "Spin button"); @@ -580,58 +579,6 @@ MainWindow.get_tab_bar ().select_tab_name ("Preview"); Tool.yield (); } - - } - - public static void test_over_path () { - Glyph g; - Path p = new Path (); - Tool pen_tool; - - pen_tool = MainWindow.get_toolbox ().get_tool ("pen_tool"); - test_select_action (pen_tool); - test_open_next_glyph (); - - g = MainWindow.get_current_glyph (); - - test_click_action (pen_tool, 3, 10, 10); - test_click_action (pen_tool, 3, 10, 10); - test_click_action (pen_tool, 3, 100, 10); - test_click_action (pen_tool, 3, 100, 100); - test_click_action (pen_tool, 3, 10, 100); - test_click_action (pen_tool, 2, 0, 0); - - g.close_path (); - - warn_if_fail (g.active_paths.size == 0); - - g.select_path (50, 50); - - warn_if_fail (g.active_paths.size == 1); - - p.add (-10, 10); - p.add (10, 10); - p.add (10, -10); - p.add (-10, -10); - p.update_region_boundaries(); - g.add_path (p); - g.close_path (); - - if (!p.is_over_coordinate (0, 0)) { - warning ("Coordinate 0, 0 is not in path."); - } - - if (!p.is_over_coordinate (-10, 10)) { - warning ("Corner corrdinate -10, 10 is not in path."); - } - - warn_if_fail (!p.is_over_coordinate (-20, -20)); - - for (double x = -10; x <= 10; x += 0.1) { - for (double y = 10; y <= 10; y += 0.1) { - warn_if_fail (p.is_over_coordinate (x, y)); - } - } }
--- /dev/null +++ b/libbirdfont/TextRendering/CachedFont.vala @@ -1,1 +1,100 @@ + /* + Copyright (C) 2015 Johan Mattsson + + 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 3 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. + */ + + using Gee; + + namespace BirdFont { + + public class CachedFont : GLib.Object { + public Font? font; + + public double top_limit { + get { return _top_limit; } + set { _top_limit = value; } + } + + public double bottom_limit { + get { return _bottom_limit; } + set { _bottom_limit = value; } + } + + public double base_line = 0; + double _top_limit = 92.77; // FIXME: load before first glyph + double _bottom_limit = -24.4; + + static FallbackFont fallback_font { + get { + if (_fallback_font == null) { + _fallback_font = new FallbackFont (); + } + + return (!) _fallback_font; + } + } + static FallbackFont? _fallback_font = null; + + public CachedFont (Font? font) { + Glyph? g; + Glyph glyph; + + this.font = font; + + g = get_glyph_by_name ("a"); + if (g != null) { + glyph = (!) g; + base_line = glyph.baseline; + top_limit = glyph.top_limit; + bottom_limit = glyph.bottom_limit; + } else { + warning("No default chararacter found in font."); + } + } + + public Glyph? get_glyph_by_name (string name) { + Glyph? g = null; + Font f; + Glyph glyph; + + if (font != null) { + f = (!) font; + g = f.get_glyph_by_name (name); + + if (g != null) { + glyph = (!) g; + glyph.top_limit = f.top_limit; + glyph.baseline = f.base_line; + glyph.bottom_limit = f.bottom_limit; + } + } + + if (g == null && name.char_count () == 1) { + f = fallback_font.get_single_glyph_font (name.get_char (0)); + g = f.get_glyph_by_name (name); + + if (g == null) { + return null; + } + + glyph = (!) g; + glyph.top_limit = f.top_limit; + glyph.baseline = f.base_line; + glyph.bottom_limit = f.bottom_limit; + } + + return g; + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/Drawing.vala @@ -1,1 +1,51 @@ + /* + Copyright (C) 2014 Johan Mattsson + + 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 3 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. + */ + + namespace BirdFont { + + /** Interface for creating drawing callbacks. */ + [Compact] + [CCode (ref_function = "bird_font_drawing_ref", unref_function = "bird_font_drawing_unref")] + public class Drawing { + + public int iterator_refcount = 1; + + public Drawing () { + } + + public void new_path (double x, double y) { + } + + public void curve_to (double xb, double yb, double xc, double yc, double xd, double yd) { + } + + public void close_path (double x, double y) { + } + + public unowned Drawing @ref () { + iterator_refcount++; + return this; + } + + public void unref () { + if (--iterator_refcount == 0) { + this.free (); + } + } + + private extern void free (); + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/FallbackFont.vala @@ -1,1 +1,311 @@ + /* + Copyright (C) 2015 Johan Mattsson + + 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 3 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. + */ + + using Gee; + + [CCode (cname = "find_font")] + public extern string? find_font (FcConfig* font_config, string characters); + + [CCode (cname = "find_font_family")] + public extern string? find_font_family (FcConfig* font_config, string characters); + + [CCode (cname = "find_font_file")] + public extern string? find_font_file (FcConfig* font_config, string font_name); + + namespace BirdFont { + + // TODO: use font config + public class FallbackFont : GLib.Object { + Gee.ArrayList<File> font_directories; + + FontFace* default_font = null; + public static FcConfig* font_config = null; + static bool font_config_started = false; + + string default_font_file_name = "Roboto-Regular.ttf"; + string default_font_family_name = "Roboto"; + + Gee.HashMap<unichar, CachePair> glyphs; + Gee.ArrayList<CachePair> cached; + + public int max_cached_fonts = 300; + + string? default_font_file = null; + + public FallbackFont () { + string home = Environment.get_home_dir (); + font_directories = new Gee.ArrayList<File> (); + + if (!font_config_started) { + font_config_started = true; + + IdleSource idle = new IdleSource (); + idle.set_callback (() => { + Task t = new Task (init_font_config); + MainWindow.native_window.run_non_blocking_background_thread (t); + return false; + }); + idle.attach (null); + } + + add_font_folder ("/usr/share/fonts/"); + add_font_folder ("/usr/local/share/fonts/"); + add_font_folder (home + "/.local/share/fonts"); + add_font_folder (home + "/.fonts"); + add_font_folder ("C:\\Windows\\Fonts"); + add_font_folder (home + "/Library/Fonts"); + add_font_folder ("/Library/Fonts"); + add_font_folder ("/Network/Library/Fonts"); + add_font_folder ("/System/Library/Fonts"); + add_font_folder ("/System Folder/Fonts"); + + glyphs = new Gee.HashMap<unichar, CachePair> (); + cached = new Gee.ArrayList<CachePair> (); + + open_default_font (); + } + + ~FallbackFont () { + if (default_font != null) { + close_font (default_font); + } + } + + public void init_font_config () { + FcConfig* config; + + #if MAC + config = FcConfigCreate(); + + string bundle = (!) BirdFont.get_settings_directory ().get_path (); + FcConfigSetSysRoot(config, bundle); + + string path = FcConfigFilename((!) SearchPaths.search_file(null, "fontconfig.settings").get_path ()); + bool loaded = FcConfigParseAndLoad(config, path, true); + + if (!loaded) { + warning ("Fontconfig initialization failed."); + } + + FcConfigSetCurrent (config); + #else + config = FcInitLoadConfigAndFonts (); + #endif + + IdleSource idle = new IdleSource (); + + idle.set_callback (() => { + font_config = config; + return false; + }); + idle.attach (null); + } + + public Font get_single_glyph_font (unichar c) { + Font f; + unichar last; + CachePair p; + + if (likely (glyphs.has_key (c))) { + p = glyphs.get (c); + + if (p.referenced < int.MAX) { + p.referenced++; + } + + return p.font; + } + + // remove glyphs from cache if it is full + if (cached.size > max_cached_fonts - 100) { + + cached.sort ((a, b) => { + CachePair pa = (CachePair) a; + CachePair pb = (CachePair) b; + return pb.referenced - pa.referenced; + }); + + int j = 0; + for (int i = cached.size - 1; i > 0; i--) { + if (j > 100) { + break; + } + + j++; + + last = cached.get (i).character; + glyphs.unset (last); + cached.remove_at (i); + } + } + + f = get_single_fallback_glyph_font (c); + p = new CachePair (f, c); + + glyphs.set (c, p); + cached.add (p); + + return (Font) f; + } + + Font get_single_fallback_glyph_font (unichar c) { + string? font_file; + BirdFontFile bf_parser; + Font bf_font; + StringBuilder? glyph_data; + FontFace* font; + + bf_font = new Font (); + font_file = null; + glyph_data = null; + + // don't use fallback font in private use area + if (0xe000 <= c <= 0xf8ff) { + return bf_font; + } + + // control characters + if (c <= 0x001f || (0x007f <= c <= 0x008d)) { + return bf_font; + } + + // check if glyph is available in roboto + if (default_font != null) { + glyph_data = get_glyph_in_font ((!) default_font, c); + } + + // use fontconfig to find a fallback font + if (glyph_data == null) { + font_file = find_font (font_config, (!) c.to_string ()); + if (font_file != null) { + font = open_font ((!) font_file); + glyph_data = get_glyph_in_font (font, c); + close_font (font); + } + } + + if (glyph_data != null) { + bf_parser = new BirdFontFile (bf_font); + bf_parser.load_data (((!) glyph_data).str); + } + + return bf_font; + } + + public StringBuilder? get_glyph_in_font (FontFace* font, unichar c) { + StringBuilder? glyph_data = null; + GlyphCollection gc; + + gc = new GlyphCollection (c, (!)c.to_string ()); + glyph_data = load_glyph (font, (uint) c); + + return glyph_data; + } + + void add_font_folder (string f) { + File folder = File.new_for_path (f); + FileInfo? file_info; + string fn; + string file_attributes; + try { + if (folder.query_exists ()) { + font_directories.add (folder); + + file_attributes = FileAttribute.STANDARD_NAME; + file_attributes += ","; + file_attributes += FileAttribute.STANDARD_TYPE; + var enumerator = folder.enumerate_children (file_attributes, 0); + + while ((file_info = enumerator.next_file ()) != null) { + fn = ((!) file_info).get_name (); + + if (((!)file_info).get_file_type () == FileType.DIRECTORY) { + add_font_folder ((!) get_child (folder, fn).get_path ()); + } + } + } + } catch (GLib.Error e) { + warning (e.message); + } + } + + File search_font_file (string font_file) { + File d, f; + + for (int i = font_directories.size - 1; i >= 0; i--) { + d = font_directories.get (i); + f = get_child (d, font_file); + + if (f.query_exists ()) { + return f; + } + } + + warning (@"The font $font_file not found"); + return File.new_for_path (font_file); + } + + public string? get_default_font_file () { + File font_file; + string? fn = null; + + if (likely (default_font_file != null)) { + return default_font_file; + } + + font_file = SearchPaths.search_file (null, default_font_file_name); + + if (font_file.query_exists ()) { + fn = (!) font_file.get_path (); + } else { + font_file = search_font_file (default_font_file_name); + + if (font_file.query_exists ()) { + fn = (!) font_file.get_path (); + } else { + fn = find_font_file (font_config, default_font_family_name); + } + } + + if (likely (fn != null)) { + default_font_file = fn; + return fn; + } + + warning(default_font_family_name + " not found"); + return null; + } + + void open_default_font () { + string? fn = get_default_font_file (); + + if (fn != null) { + default_font = open_font ((!) fn); + } + } + + class CachePair : GLib.Object { + public Font font; + public unichar character; + public int referenced = 1; + + public CachePair (Font f, unichar c) { + font = f; + character = c; + } + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/FontCache.vala @@ -1,1 +1,84 @@ + /* + Copyright (C) 2014 Johan Mattsson + + 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 3 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. + */ + + using Gee; + + namespace BirdFont { + + /** Thread specific font cache. */ + public class FontCache { + public static FallbackFont fallback_font; + + static FontCache? default_cache = null; + Gee.HashMap<string, CachedFont> fonts; + CachedFont fallback; + + public FontCache () { + if (is_null (fallback_font)) { + fallback_font = new FallbackFont (); + } + + fallback = new CachedFont (null); + fonts = new Gee.HashMap<string, CachedFont> (); + } + + public CachedFont get_font (string file_name) { + CachedFont c; + Font f; + bool ok; + + if (file_name == "") { + return fallback; + } + + if (fonts.has_key (file_name)) { + c = fonts.get (file_name); + return c; + } + + f = new Font (); + f.set_file (file_name); + ok = f.load (); + if (!ok) { + stderr.printf ("Can't load %s\n", file_name); + return new CachedFont (null); + } + + c = new CachedFont (f); + + if (file_name == "") { + warning ("No file."); + return c; + } + + fonts.set (file_name, c); + return c; + } + + public static FontCache get_default_cache () { + if (default_cache == null) { + default_cache = new FontCache (); + } + + return (!) default_cache; + } + + public CachedFont get_fallback () { + return fallback; + } + + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/LineTextArea.vala @@ -1,1 +1,34 @@ + /* + Copyright (C) 2014 Johan Mattsson + + 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 3 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. + */ + + using Cairo; + using Math; + + namespace BirdFont { + + public class LineTextArea : TextArea { + + public LineTextArea (double size) { + base (size); + + single_line = true; + min_height = size; + height = min_height; + + layout (); + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/Text.vala @@ -1,1 +1,593 @@ + /* + Copyright (C) 2014 2015 Johan Mattsson + + 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 3 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. + */ + + using Cairo; + using Math; + + namespace BirdFont { + + /** Test implementation of a birdfont rendering engine. */ + public class Text : Widget { + FontCache font_cache; + public CachedFont cached_font; + + Surface? cache = null; + + public string text; + + private bool use_cache = true; + + GlyphSequence glyph_sequence { + get { + if (gs == null) { + gs = generate_glyphs (); + } + + return (!) gs; + } + } + + Gee.ArrayList<string> glyph_names; + GlyphSequence? gs = null; + + public delegate void Iterator (Glyph glyph, double kerning, bool last); + public double font_size; + double sidebearing_extent = 0; + + public double r = 0; + public double g = 0; + public double b = 0; + public double a = 1; + double truncated_width = -1; + + double margin_left = 0; + + public Text (string text = "", double size = 17, double margin_bottom = 0, Color? color = null) { + this.margin_bottom = margin_bottom; + font_cache = FontCache.get_default_cache (); + cached_font = font_cache.get_fallback (); + + if (color != null) { + Color c = (!) color; + r = c.r; + g = c.g; + b = c.b; + a = c.a; + } + + set_text (text); + set_font_size (size); + } + + public void set_use_cache (bool cache) { + use_cache = cache; + } + + public string get_text () { + return text; + } + + /** Set font for this text area. + * @param font_absolute path to the font file or a file name for one of the font files in search paths. + * @return true if the font was found + */ + public bool load_font (string font_file) { + File path; + File f; + FontCache fc; + + f = File.new_for_path (font_file); + path = (f.query_exists ()) ? f : SearchPaths.find_file (null, font_file); + + fc = FontCache.get_default_cache (); + cached_font = fc.get_font ((!) path.get_path ()); + gs = generate_glyphs (); + + return cached_font.font != null; + } + + public void set_font_size (double height_in_pixels) { + font_size = height_in_pixels; + sidebearing_extent = 0; + + if (gs == null) { // ensure height is loaded for the font + gs = generate_glyphs (); + } + } + + public void set_font_cache (FontCache font_cache) { + this.font_cache = font_cache; + } + + public void set_text (string text) { + this.text = text; + gs = null; + sidebearing_extent = 0; + cache = null; + } + + private GlyphSequence generate_glyphs () { + int index; + unichar c; + string name; + Glyph? g; + GlyphSequence gs; + + gs = new GlyphSequence (); + + glyph_names = new Gee.ArrayList<string> (); + index = 0; + while (text.get_next_char (ref index, out c)) { + name = (!) c.to_string (); + g = cached_font.get_glyph_by_name (name); + + gs.glyph.add (g); + glyph_names.add (name); + } + + return gs; + } + + public void iterate (Iterator iter) { + Glyph glyph; + double w, kern; + int wi; + Glyph? prev; + Glyph? g; + GlyphSequence word_with_ligatures; + GlyphRange? gr_left, gr_right; + GlyphSequence word; + KerningClasses kc; + Font empty = Font.empty; + + glyph = new Glyph.no_lines ("", '\0'); + + w = 0; + prev = null; + kern = 0; + + word = glyph_sequence; + wi = 0; + + if (cached_font.font != null) { + word_with_ligatures = word.process_ligatures ((!) cached_font.font); + } else { + word_with_ligatures = word.process_ligatures (new Font ()); + } + + gr_left = null; + gr_right = null; + + if (cached_font.font != null) { + kc = ((!) cached_font.font).get_kerning_classes (); + } else { + kc = new KerningClasses (empty); + } + + if (word_with_ligatures.glyph.size > 0) { + g = word_with_ligatures.glyph.get (0); + if (g != null) { + margin_left = ((!) g).get_left_side_bearing (); + + if (margin_left < 0) { + margin_left = -margin_left; + } else { + margin_left = 0; + } + } + } + + for (int i = 0; i < word_with_ligatures.glyph.size; i++) { + g = word_with_ligatures.glyph.get (i); + + if (g == null || prev == null || wi == 0) { + kern = 0; + } else { + return_if_fail (wi < word_with_ligatures.ranges.size); + return_if_fail (wi - 1 >= 0); + + gr_left = word_with_ligatures.ranges.get (wi - 1); + gr_right = word_with_ligatures.ranges.get (wi); + + kern = kc.get_kerning_for_pair (((!) prev).get_name (), ((!) g).get_name (), gr_left, gr_right); + } + + // process glyph + if (g == null && (0 <= i < glyph_names.size)) { + g = cached_font.get_glyph_by_name (glyph_names.get (i)); + } + + glyph = (g == null) ? new Glyph ("") : (!) g; + iter (glyph, kern, i + 1 == word_with_ligatures.glyph.size); + prev = g; + wi++; + } + } + + // FIXME: some fonts doesn't have on curve extrema + public double get_extent () { + double x = 0; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double lsb; + + lsb = glyph.left_limit; + + if (!last) { + x += (glyph.get_width () + kerning) * get_scale (glyph); + } else { + glyph.boundaries (out x1, out y1, out x2, out y2); + x += (x2 - lsb) * get_scale (glyph); + } + }); + + return x; + } + + public double get_sidebearing_extent () { + double x ; + + if (likely (sidebearing_extent > 0)) { + return sidebearing_extent; + } + + x = 0; + + iterate ((glyph, kerning, last) => { + double lsb; + lsb = glyph.left_limit; + x += (glyph.get_width () + kerning) * get_scale (glyph); + }); + + sidebearing_extent = x; + return x; + } + + public override double get_height () { + return font_size; + } + + public double get_acender () { + double max_height = 0; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double h; + glyph.boundaries (out x1, out y1, out x2, out y2); + h = Math.fmax (y1, y2) - Math.fmin (y1, y2); + h *= get_scale (glyph) - glyph.baseline * get_scale (glyph); + if (h > max_height) { + max_height = h; + } + }); + + return max_height; + } + + public override double get_width () { + double x = 0; + bool first = true; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double lsb; + + lsb = glyph.left_limit; + + if (first) { + glyph.boundaries (out x1, out y1, out x2, out y2); + x += (glyph.get_width () + kerning - Math.fmin (x1, x2)) * get_scale (glyph); + first = false; + } else if (!last) { + x += (glyph.get_width () + kerning) * get_scale (glyph); + } else { + glyph.boundaries (out x1, out y1, out x2, out y2); + x += (x2 - lsb) * get_scale (glyph); + } + }); + + return x; + } + + public double get_decender () { + double decender_max = get_max_decender (); + return decender_max > 0 ? decender_max : 0; + } + + private double get_max_decender () { + double decender = 0; + double decender_max = 0; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double y; + glyph.boundaries (out x1, out y1, out x2, out y2); + y = Math.fmin (y1, y2); + decender = (glyph.baseline - y) * get_scale (glyph); + if (decender > decender_max) { + decender_max = decender; + } + }); + + return decender_max; + } + + public override void draw (Context cr) { + double descender = cached_font.bottom_limit + cached_font.base_line; + double y = widget_y + get_height () + get_font_scale () * descender; // FIXME: + draw_at_baseline (cr, widget_x, y); + } + + public void draw_at_top (Context cr, double px, double py, string cacheid = "") { + double s = get_font_scale (); + double y = py + s * (cached_font.top_limit - cached_font.base_line); + draw_at_baseline (cr, px, y, cacheid); + } + + public void set_source_rgba (double r, double g, double b, double a) { + if (this.r != r || + this.g != g || + this.b != b || + this.a != a) { + + this.r = r; + this.g = g; + this.b = b; + this.a = a; + cache = null; + } + } + + public string get_cache_id (int offset_x, int offset_y) { + string key; + int64 c; + + c = (((int64) (r * 255)) << 24) + | (((int64) (g * 255)) << 16) + | (((int64) (b * 255)) << 8) + | (((int64) (a * 255)) << 0); + + // FIXME: use binary key + key = @"$font_size $c $offset_x $offset_y"; + + return key; + } + + public void draw_at_baseline (Context cr, double px, double py, string cacheid = "") { + if (cache == null) { + cache = draw_on_cache_surface (cacheid); + } + + double screen_scale = Screen.get_scale (); + double font_scale = get_font_scale (); + double cache_y = py - font_scale * (cached_font.top_limit - cached_font.base_line); + + cr.save(); + cr.scale (1 / screen_scale, 1 / screen_scale); + double scaled_x = (px - margin_left) * screen_scale; + double scaled_y = cache_y * screen_scale; + cr.set_source_surface ((!) cache, (int) rint (scaled_x), (int) rint (scaled_y)); + cr.paint (); + cr.restore(); + } + + Surface draw_on_cache_surface (string cacheid) { + double y; + double ratio; + double cc_y; + Context cr; + Surface cache_surface; + double screen_scale = Screen.get_scale(); + double h = font_size * screen_scale + 1; + + ratio = get_font_scale (); + cc_y = (cached_font.top_limit - cached_font.base_line) * ratio; + + // double x = margin_left * ratio; + double x = 0; + double w = get_sidebearing_extent () * screen_scale + x + margin_left + 1; + + cache_surface = Screen.create_background_surface ((int) w, (int) h); + cr = new Context (cache_surface); + cr.scale (screen_scale, screen_scale); + + y = cc_y; + + if (unlikely (cached_font.base_line != 0)) { + warning ("Base line not zero."); + } + + iterate ((glyph, kerning, last) => { + double end; + + x += kerning * ratio; + end = x + glyph.get_width () * ratio; + + // truncation + if (truncated_width > 0 && end > truncated_width) { + return; + } + + if (use_cache) { + draw_chached (cr, glyph, kerning, last, x, y, cc_y, + ratio, cacheid); + } else { + draw_without_cache (cr, glyph, kerning, last, x, y, cc_y, ratio); + } + + x = end; + }); + + return cache_surface; + } + + void draw_without_cache (Context cr, Glyph glyph, double kerning, bool last, + double x, double y, double cc_y, double ratio) { + + double lsb; + + cr.save (); + cr.set_source_rgba (r, g, b, a); + cr.new_path (); + + lsb = glyph.left_limit; + + foreach (Path path in glyph.get_visible_paths ()) { + draw_path (cr, glyph, path, lsb, x, y, ratio); + } + + cr.fill (); + cr.restore (); + + } + + void draw_chached (Context cr, Glyph glyph, double kerning, bool last, + double x, double y, double cc_y, double ratio, + string cacheid = "") { + + double lsb; + Surface cache; + Surface cached_glyph; + Context cc; + string cache_id; + double glyph_margin_left = glyph.get_left_side_bearing (); + + if (glyph_margin_left < 0) { + glyph_margin_left = -glyph_margin_left; + } else { + glyph_margin_left = 0; + } + + double xp = (x - glyph_margin_left * ratio) * Screen.get_scale (); + double yp = (y - cc_y) * Screen.get_scale (); + int offset_x, offset_y; + + offset_x = (int) (10 * (xp - (int) xp)); + offset_y = (int) (10 * (yp - (int) yp)); + + cache_id = (cacheid == "") ? get_cache_id (offset_x, offset_y) : cacheid; + + if (!glyph.has_cache (cache_id)) { + int w = (int) ((2 * glyph_margin_left * ratio + glyph.get_width ()) * ratio) + 2; + int h = (int) font_size + 2; + cache = Screen.create_background_surface (w, h); + cc = new Context (cache); + + cc.scale(Screen.get_scale (), Screen.get_scale ()); + + lsb = glyph.left_limit - glyph_margin_left; + + cc.save (); + cc.set_source_rgba (r, g, b, a); + cc.new_path (); + + foreach (Path path in glyph.get_visible_paths ()) { + draw_path (cc, glyph, path, lsb, glyph_margin_left * ratio + offset_x / 10.0, cc_y + offset_y / 10.0, ratio); + } + + cc.fill (); + cc.restore (); + + if (use_cache) { + glyph.set_cache (cache_id, cache); + } + + cached_glyph = cache; + } else { + cached_glyph = glyph.get_cache (cache_id); + } + + cr.save (); + cr.set_antialias (Cairo.Antialias.NONE); + cr.scale(1 / Screen.get_scale (), 1 / Screen.get_scale ()); + cr.set_source_surface (cached_glyph, + (int) xp, + (int) yp); + cr.paint (); + cr.restore (); + } + + void draw_path (Context cr, Glyph glyph, Path path, + double lsb, double x, double y, double scale) { + + EditPoint e, prev; + double xa, ya, xb, yb, xc, yc, xd, yd; + double by; + double s = get_scale (glyph); + + if (path.points.size > 0) { + if (unlikely (path.is_open ())) { + warning (@"Path is open in $(glyph.get_name ())."); + } + + //path.add_hidden_double_points (); // FIXME: this distorts shapes + + prev = path.points.get (path.points.size - 1); + xa = (prev.x - lsb) * s + x; + ya = y - prev.y * s; + cr.move_to (xa, ya); + + by = (y - cached_font.base_line * s); + for (int i = 0; i < path.points.size; i++) { + e = path.points.get (i).copy (); + + PenTool.convert_point_segment_type (prev, e, PointType.CUBIC); + + xb = (prev.get_right_handle ().x - lsb) * s + x; + yb = by - prev.get_right_handle ().y * s; + + xc = (e.get_left_handle ().x - lsb) * s + x; + yc = by - e.get_left_handle ().y * s; + + xd = (e.x - lsb) * s + x; + yd = by - e.y * s; + + cr.curve_to (xb, yb, xc, yc, xd, yd); + cr.line_to (xd, yd); + + prev = e; + } + } + } + + public double get_baseline_to_bottom (Glyph g) { + return get_scale (g) * (-g.baseline - g.bottom_limit); + } + + public double get_scale (Glyph g) { + double s = g.top_limit - g.bottom_limit; + + if (s == 0) { + s = cached_font.top_limit - cached_font.bottom_limit; + } + + return font_size / s; + } + + public double get_font_scale () { + return font_size / (cached_font.top_limit - cached_font.bottom_limit); + } + + public double get_baseline_to_bottom_for_font () { + return get_font_scale () * (-cached_font.base_line - cached_font.bottom_limit); + } + + public void truncate (double max_width) { + truncated_width = max_width; + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/TextArea.vala @@ -1,1 +1,1660 @@ + /* + Copyright (C) 2014 Johan Mattsson + + 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 3 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. + */ + + using Cairo; + using Math; + + namespace BirdFont { + + public class TextArea : Widget { + + public double min_width = 500; + public double min_height = 100; + public double font_size; + public double padding = 3.3; + public bool single_line = false; + + protected Color text_color = Color.black (); + + public bool draw_carret { + get { return carret_is_visible; } + set { + carret_is_visible = value; + if (!value) { + update_selection = false; + selection_end = carret.copy (); + } + } + } + public bool carret_is_visible = false; + public bool draw_border = true; + + public double width; + public double height; + + Carret carret = new Carret (); + Carret selection_end = new Carret (); + bool update_selection = false; + public bool show_selection = false; + + public signal void scroll (double pixels); + public signal void text_changed (string text); + public signal void enter (string text); + + Gee.ArrayList<Paragraph> paragraphs = new Gee.ArrayList<Paragraph> (); + private static const int DONE = -2; + + int last_paragraph = 0; + string text; + int text_length; + + Gee.ArrayList<TextUndoItem> undo_items = new Gee.ArrayList<TextUndoItem> (); + Gee.ArrayList<TextUndoItem> redo_items = new Gee.ArrayList<TextUndoItem> (); + + bool store_undo_state_at_next_event = false; + + public bool editable; + + public TextArea (double font_size = 20, Color? c = null) { + this.font_size = font_size; + width = min_width; + height = min_height; + editable = true; + + if (c != null) { + text_color = (!) c; + } + } + + public override void focus (bool focus) { + draw_carret = focus; + } + + public override double get_height () { + return height + 2 * padding; + } + + public override double get_width () { + return width + 2 * padding; + } + + public void set_font_size (double z) { + font_size = z; + } + + bool generate_paragraphs () { + Paragraph paragraph; + + int next_paragraph = -1; + + if (is_null (text)) { + warning ("No text"); + return false; + } + + if (last_paragraph == DONE) { + return false; + } + + next_paragraph = text.index_of ("\n", last_paragraph); + + if (next_paragraph == -1) { + paragraph = new Paragraph (text.substring (last_paragraph), font_size, paragraphs.size, text_color); + paragraphs.add (paragraph); + last_paragraph = DONE; + } else { + next_paragraph += "\n".length; + paragraph = new Paragraph (text.substring (last_paragraph, next_paragraph - last_paragraph), font_size, paragraphs.size, text_color); + paragraphs.add (paragraph); + last_paragraph = next_paragraph; + } + + return last_paragraph != DONE; + } + + void generate_all_paragraphs () { + while (generate_paragraphs ()) { + } + } + + public override void key_press (uint keyval) { + unichar c; + TextUndoItem ui; + + if (!editable) { + return; + } + + c = (unichar) keyval; + + switch (c) { + case ' ': + store_undo_edit_state (); + add_character (keyval); + break; + case 'a': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + select_all (); + } else { + add_character (keyval); + } + break; + case 'c': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + ClipTool.copy_text (this); + } else { + add_character (keyval); + } + break; + case 'v': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + ClipTool.paste_text (this); + store_undo_state_at_next_event = true; + } else { + add_character (keyval); + } + break; + case 'y': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + redo (); + } else { + add_character (keyval); + } + break; + case 'z': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + undo (); + } else { + add_character (keyval); + } + break; + case Key.RIGHT: + check_selection (); + move_carret_next (); + break; + case Key.LEFT: + check_selection (); + move_carret_previous (); + break; + case Key.DOWN: + check_selection (); + move_carret_next_row (); + break; + case Key.UP: + check_selection (); + move_carret_previous_row (); + break; + case Key.END: + check_selection (); + move_carret_to_end_of_line (); + break; + case Key.HOME: + check_selection (); + move_carret_to_beginning_of_line (); + break; + case Key.BACK_SPACE: + if (has_selection ()) { + ui = delete_selected_text (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } else { + ui = remove_last_character (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } + text_changed (get_text ()); + break; + case Key.ENTER: + store_undo_edit_state (); + insert_text ("\n"); + + if (single_line) { + enter (get_text ()); + } + break; + case Key.DEL: + if (has_selection ()) { + ui = delete_selected_text (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } else { + ui = remove_next_character (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } + text_changed (get_text ()); + break; + default: + if (!KeyBindings.has_ctrl () && !KeyBindings.has_logo ()) { + add_character (keyval); + } + break; + } + + GlyphCanvas.redraw (); + } + + void check_selection () { + if (!has_selection () && KeyBindings.has_shift ()) { + show_selection = true; + selection_end = carret.copy (); + } + + if (!KeyBindings.has_shift ()) { + show_selection = false; + } + } + + private void add_character (uint keyval) { + unichar c = (unichar) keyval; + string s; + + if (!is_modifier_key (keyval) + && !KeyBindings.has_ctrl () + && !KeyBindings.has_alt ()) { + + s = (!) c.to_string (); + + if (s.validate ()) { + if (store_undo_state_at_next_event) { + store_undo_edit_state (); + store_undo_state_at_next_event = false; + } + + insert_text (s); + } + } + } + + Paragraph get_current_paragraph () { + Paragraph p; + + if (unlikely (!(0 <= carret.paragraph < paragraphs.size))) { + warning (@"No paragraph, index: $(carret.paragraph), size: $(paragraphs.size)"); + p = new Paragraph ("", 0, 0, text_color); + paragraphs.add (p); + return p; + } + + p = paragraphs.get (carret.paragraph); + return p; + } + + public void set_text (string t) { + int tl; + + if (single_line) { + text = t.replace ("\n", "").replace ("\r", ""); + } else { + text = t; + } + + tl = t.length; + text_length += tl; + + paragraphs.clear (); + generate_paragraphs (); + + return_if_fail (paragraphs.size != 0); + + carret.paragraph = paragraphs.size - 1; + carret.character_index = paragraphs.get (paragraphs.size - 1).text.length; + selection_end = carret.copy (); + show_selection = false; + + text_changed (get_text ()); + } + + Carret get_selection_start () { + if (carret.paragraph == selection_end.paragraph) { + return carret.character_index < selection_end.character_index ? carret : selection_end; + } + + return carret.paragraph < selection_end.paragraph ? carret : selection_end; + } + + Carret get_selection_stop () { + if (carret.paragraph == selection_end.paragraph) { + return carret.character_index > selection_end.character_index ? carret : selection_end; + } + + return carret.paragraph > selection_end.paragraph ? carret : selection_end; + } + + public string get_selected_text () { + Carret selection_start, selection_stop; + int i; + Paragraph pg; + StringBuilder sb; + + sb = new StringBuilder (); + + if (!has_selection ()) { + return "".dup (); + } + + selection_start = get_selection_start (); + selection_stop = get_selection_stop (); + + if (selection_start.paragraph == selection_stop.paragraph) { + pg = paragraphs.get (selection_start.paragraph); + return pg.text.substring (selection_start.character_index, selection_stop.character_index - selection_start.character_index); + } + + pg = paragraphs.get (selection_start.paragraph); + sb.append (pg.text.substring (selection_start.character_index)); + + for (i = selection_start.paragraph + 1; i < selection_stop.paragraph; i++) { + return_val_if_fail (0 <= i < paragraphs.size, "".dup ()); + pg = paragraphs.get (i); + sb.append (pg.text); + } + + pg = paragraphs.get (selection_stop.paragraph); + sb.append (pg.text.substring (0, selection_stop.character_index)); + + return sb.str; + } + + public void select_all () { + while (last_paragraph != DONE) { + generate_paragraphs (); + } + + if (paragraphs.size > 0) { + carret.paragraph = 0; + carret.character_index = 0; + selection_end.paragraph = paragraphs.size - 1; + selection_end.character_index = paragraphs.get (paragraphs.size - 1).text_length; + show_selection = true; + } + } + + public TextUndoItem delete_selected_text () { + Carret selection_start, selection_stop; + int i; + Paragraph pg, pge; + string e, s, n; + bool same; + TextUndoItem ui; + + ui = new TextUndoItem (carret); + + e = ""; + s = ""; + n = ""; + + if (!has_selection ()) { + warning ("No selected text."); + return ui; + } + + selection_start = get_selection_start (); + selection_stop = get_selection_stop (); + + same = selection_start.paragraph == selection_stop.paragraph; + + if (!same) { + return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); + pg = paragraphs.get (selection_start.paragraph); + s = pg.text.substring (0, selection_start.character_index); + + return_val_if_fail (0 <= selection_stop.paragraph < paragraphs.size, ui); + pge = paragraphs.get (selection_stop.paragraph); + e = pge.text.substring (selection_stop.character_index); + + if (!s.has_suffix ("\n")) { + ui.deleted.add (pge.copy ()); + ui.edited.add (pg.copy ()); + + pg.set_text (s + e); + pge.set_text (""); + } else { + ui.edited.add (pg.copy ()); + ui.edited.add (pge.copy ()); + + pg.set_text (s); + pge.set_text (e); + } + } else { + return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); + + pg = paragraphs.get (selection_start.paragraph); + n = pg.text.substring (0, selection_start.character_index); + n += pg.text.substring (selection_stop.character_index); + + if (n == "") { + ui.deleted.add (pg.copy ()); + paragraphs.remove_at (selection_start.paragraph); + } else { + ui.edited.add (pg.copy ()); + } + + pg.set_text (n); + } + + if (e == "" && !same) { + paragraphs.remove_at (selection_stop.paragraph); + } + + for (i = selection_stop.paragraph - 1; i > selection_start.paragraph; i--) { + return_val_if_fail (0 <= i < paragraphs.size, ui); + ui.deleted.add (paragraphs.get (i)); + paragraphs.remove_at (i); + } + + if (s == "" && !same) { + return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); + paragraphs.remove_at (selection_start.paragraph); + } + + carret = selection_start.copy (); + selection_end = carret.copy (); + + show_selection = false; + update_paragraph_index (); + layout (); + + return ui; + } + + void update_paragraph_index () { + int i = 0; + foreach (Paragraph p in paragraphs) { + p.index = i; + i++; + } + } + + public TextUndoItem remove_last_character () { + TextUndoItem ui; + move_carret_previous (); + ui = remove_next_character (); + return ui; + } + + public TextUndoItem remove_next_character () { + Paragraph paragraph; + Paragraph next_paragraph; + int index; + unichar c; + string np; + TextUndoItem ui; + + ui = new TextUndoItem (carret); + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, ui); + paragraph = paragraphs.get (carret.paragraph); + + index = carret.character_index; + + paragraph.text.get_next_char (ref index, out c); + + if (index >= paragraph.text_length) { + np = paragraph.text.substring (0, carret.character_index); + + if (carret.paragraph + 1 < paragraphs.size) { + next_paragraph = paragraphs.get (carret.paragraph + 1); + paragraphs.remove_at (carret.paragraph + 1); + + np = np + next_paragraph.text; + + ui.deleted.add (next_paragraph); + } + + paragraph.set_text (np); + ui.edited.add (paragraph); + } else { + np = paragraph.text.substring (0, carret.character_index) + paragraph.text.substring (index); + paragraph.set_text (np); + + if (np == "") { + return_val_if_fail (carret.paragraph > 0, ui); + carret.paragraph--; + paragraph = paragraphs.get (carret.paragraph); + carret.character_index = paragraph.text_length; + + ui.deleted.add (paragraphs.get (carret.paragraph + 1)); + + paragraphs.remove_at (carret.paragraph + 1); + } else { + ui.edited.add (paragraph); + } + } + + update_paragraph_index (); + layout (); + + return ui; + } + + public void move_carret_next () { + unichar c; + + move_carret_one_character (); + + if (KeyBindings.has_ctrl ()) { + while (true) { + c = move_carret_one_character (); + + if (c == '\0' || c == ' ') { + break; + } + } + } + } + + unichar move_carret_one_character () { + Paragraph paragraph; + int index; + unichar c; + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + paragraph = paragraphs.get (carret.paragraph); + + index = carret.character_index; + + paragraph.text.get_next_char (ref index, out c); + + if (index >= paragraph.text_length && carret.paragraph + 1 < paragraphs.size) { + carret.paragraph++; + carret.character_index = 0; + c = ' '; + } else { + carret.character_index = index; + } + + return c; + } + + public void move_carret_previous () { + unichar c; + + move_carret_back_one_character (); + + if (KeyBindings.has_ctrl ()) { + while (true) { + c = move_carret_back_one_character (); + + if (c == '\0' || c == ' ') { + break; + } + } + } + } + + unichar move_carret_back_one_character () { + Paragraph paragraph; + int index, last_index; + unichar c; + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + paragraph = paragraphs.get (carret.paragraph); + + index = 0; + last_index = -1; + + while (paragraph.text.get_next_char (ref index, out c) && index < carret.character_index) { + last_index = index; + } + + if (last_index <= 0 && carret.paragraph > 0) { + carret.paragraph--; + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + paragraph = paragraphs.get (carret.paragraph); + carret.character_index = paragraph.text_length; + + if (paragraph.text.has_suffix ("\n")) { + carret.character_index -= "\n".length; + } + + c = ' '; + } else if (last_index > 0) { + carret.character_index = last_index; + } else { + carret.character_index = 0; + c = ' '; + } + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + + return c; + } + + public void move_carret_next_row () { + double nr = font_size; + + if (carret.desired_y + 2 * font_size >= allocation.height) { + scroll (2 * font_size); + nr = -font_size; + } + + if (carret.desired_y + nr < widget_y + height - padding) { + carret = get_carret_at (carret.desired_x - widget_x - padding, carret.desired_y + nr); + } + } + + public void move_carret_to_end_of_line () { + carret = get_carret_at (widget_x + padding + width, carret.desired_y, false); + } + + public void move_carret_to_beginning_of_line () { + carret = get_carret_at (widget_x, carret.desired_y, false); + } + + public void move_carret_previous_row () { + double nr = -font_size; + + if (carret.desired_y - 2 * font_size < 0) { + scroll (-2 * font_size); + nr = font_size; + } + + if (carret.desired_y + nr > widget_y + padding) { + carret = get_carret_at (carret.desired_x, carret.desired_y + nr); + } + } + + public bool has_selection () { + return show_selection && selection_is_visible (); + } + + private bool selection_is_visible () { + return carret.paragraph != selection_end.paragraph || carret.character_index != selection_end.character_index; + } + + public void insert_text (string t) { + string s; + Paragraph paragraph; + TextUndoItem ui; + Gee.ArrayList<string> pgs; + bool u = false; + + pgs = new Gee.ArrayList<string> (); + + if (single_line) { + s = t.replace ("\n", "").replace ("\r", ""); + pgs.add (s); + } else { + if (t.last_index_of ("\n") > 0) { + string[] parts = t.split ("\n"); + int i; + for (i = 0; i < parts.length -1; i++) { + pgs.add (parts[i]); + pgs.add ("\n"); + } + + pgs.add (parts[parts.length - 1]); + + if (t.has_suffix ("\n")) { + pgs.add ("\n"); + } + } else { + s = t; + pgs.add (s); + } + } + + if (has_selection () && show_selection) { + ui = delete_selected_text (); + u = true; + + if (paragraphs.size == 0) { + paragraphs.add (new Paragraph ("", font_size, 0, text_color)); + } + } else { + ui = new TextUndoItem (carret); + } + + return_if_fail (0 <= carret.paragraph < paragraphs.size); + paragraph = paragraphs.get (carret.paragraph); + + if (pgs.size > 0) { + if (!u) { + ui.edited.add (paragraph.copy ()); + } + + string first = pgs.get (0); + + string end; + string nt = paragraph.text.substring (0, carret.character_index); + + nt += first; + end = paragraph.text.substring (carret.character_index); + + paragraph.set_text (nt); + + int paragraph_index = carret.paragraph; + Paragraph next_paragraph = paragraph; + for (int i = 1; i < pgs.size; i++) { + paragraph_index++; + string next = pgs.get (i); + next_paragraph = new Paragraph (next, font_size, paragraph_index, text_color); + paragraphs.insert (paragraph_index, next_paragraph); + ui.added.add (next_paragraph); + u = true; + } + + carret.paragraph = paragraph_index; + carret.character_index = next_paragraph.text.length; + + next_paragraph.set_text (next_paragraph.text + end); + } + + if (u) { + undo_items.add (ui); + redo_items.clear (); + } + + update_paragraph_index (); + layout (); + + text_changed (get_text ()); + show_selection = false; + } + + public string get_text () { + StringBuilder sb = new StringBuilder (); + + generate_all_paragraphs (); + + foreach (Paragraph p in paragraphs) { + sb.append (p.text); + } + + return sb.str; + } + + Carret get_carret_at (double click_x, double click_y, + bool check_boundaries = true) { + + int i = 0; + double tx, ty; + double p; + string w; + int ch_index; + double min_d = double.MAX; + Carret c = new Carret (); + double dt; + + c.paragraph = -1; + c.desired_x = click_x; + c.desired_y = click_y; + + foreach (Paragraph paragraph in paragraphs) { + if (!check_boundaries || paragraph.text_is_on_screen (allocation, widget_y)) { + ch_index = 0; + + if (paragraph.start_y + widget_y - font_size <= click_y <= paragraph.end_y + widget_y + font_size) { + foreach (Text next_word in paragraph.words) { + double tt_click = click_y - widget_y - padding + font_size; + + w = next_word.text; + + if (next_word.widget_y <= tt_click <= next_word.widget_y + font_size) { + + p = next_word.get_sidebearing_extent (); + + if ((next_word.widget_y <= tt_click <= next_word.widget_y + font_size) + && (next_word.widget_x + widget_x <= click_x <= next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())) { + + tx = widget_x + next_word.widget_x + padding; + ty = widget_y + next_word.widget_y + padding; + + next_word.iterate ((glyph, kerning, last) => { + double cw; + int ci; + double d; + string gc = (!) glyph.get_unichar ().to_string (); + + d = Math.fabs (click_x - tx); + + if (d <= min_d) { + min_d = d; + c.character_index = ch_index; + c.paragraph = i; + } + + cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; + ci = gc.length; + + tx += cw; + ch_index += ci; + }); + + dt = Math.fabs (click_x - (tx + widget_x + padding)); + if (dt < min_d) { + min_d = dt; + c.character_index = ch_index; + c.paragraph = i; + } + } else { + dt = Math.fabs (click_x - (next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())); + + if (dt < min_d) { + min_d = dt; + c.character_index = ch_index + w.length; + + if (w.has_suffix ("\n")) { + c.character_index -= "\n".length; + } + + c.paragraph = i; + } + + ch_index += w.length; + } + } else { + ch_index += w.length; + } + } + } + } + i++; + } + + if (unlikely (c.paragraph < 0)) { + c.paragraph = paragraphs.size > 0 ? paragraphs.size - 1 : 0; + c.character_index = paragraphs.size > 0 ? paragraphs.get (c.paragraph).text.length : 0; + } + + store_undo_state_at_next_event = true; + + return c; + } + + /** @return offset to click in text. */ + public override void layout () { + double p; + double tx, ty; + string w; + double xmax = 0; + int i = 0; + double dd; + + tx = 0; + ty = font_size; + + if (allocation.width <= 0 || allocation.height <= 0) { + warning ("Parent widget allocation is not set."); + } + + for (i = paragraphs.size - 1; i >= 0 && paragraphs.size > 1; i--) { + if (unlikely (paragraphs.get (i).is_empty ())) { + paragraphs.remove_at (i); + update_paragraph_index (); + } + } + + i = 0; + foreach (Paragraph paragraph in paragraphs) { + if (paragraph.need_layout + || (paragraph.text_area_width != width + && paragraph.text_is_on_screen (allocation, widget_y))) { + + paragraph.start_y = ty; + paragraph.start_x = tx; + + paragraph.cached_surface = null; + + foreach (Text next_word in paragraph.words) { + next_word.set_font_size (font_size); + + w = next_word.text; + p = next_word.get_sidebearing_extent (); + + if (unlikely (p == 0)) { + warning (@"Zero width word: $(w)"); + } + + if (w == "") { + break; + } + + if (w == "\n") { + next_word.widget_x = tx; + next_word.widget_y = ty; + + tx = 0; + ty += next_word.font_size; + } else { + if (!single_line) { + if (tx + p + 2 * padding > width || w == "\n") { + tx = 0; + ty += next_word.font_size; + } + } + + if (tx + p > xmax) { + xmax = tx + p; + } + + next_word.widget_x = tx; + next_word.widget_y = ty; + + if (w != "\n") { + tx += p; + } + } + } + + if (tx > xmax) { + xmax = tx; + } + + paragraph.text_area_width = width; + paragraph.width = xmax; + paragraph.end_x = tx; + paragraph.end_y = ty; + paragraph.need_layout = false; + } + + if (xmax > width) { + break; + } + + tx = paragraph.end_x; + ty = paragraph.end_y; + i++; + } + + if (xmax > width) { + this.width = xmax + 2 * padding; + layout (); + return; + } + + this.height = fmax (min_height, ty + 2 * padding); + + if (last_paragraph != DONE) { + this.height = (text_length / (double) last_paragraph) * ty + 2 * padding; // estimate height + } + + if (ty + widget_y < allocation.height && last_paragraph != DONE) { + generate_paragraphs (); + layout (); + return; + } + + ty = font_size; + tx = 0; + + foreach (Paragraph paragraph in paragraphs) { + dd = ty - paragraph.start_y; + + if (dd != 0) { + paragraph.start_y += dd; + paragraph.end_y += dd; + foreach (Text word in paragraph.words) { + word.widget_y += dd; + } + } + + ty = paragraph.end_y; + } + } + + public override void button_press (uint button, double x, double y) { + if (is_over (x, y)) { + carret = get_carret_at (x, y); + selection_end = carret.copy (); + update_selection = true; + } + } + + public override void button_release (uint button, double x, double y) { + update_selection = false; + show_selection = selection_is_visible (); + } + + public override bool motion (double x, double y) { + if (update_selection) { + selection_end = get_carret_at (x, y); + show_selection = selection_is_visible (); + } + + return update_selection; + } + + public override void draw (Context cr) { + Text word; + double tx, ty; + string w; + double scale; + double width; + double x = widget_x; + double y = widget_y; + Carret selection_start, selection_stop; + double carret_x; + double carret_y; + + layout (); + + if (draw_border) { + // background + cr.save (); + cr.set_line_width (1); + Theme.color (cr, "Text Area Background"); + draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); + cr.fill (); + cr.restore (); + + // border + cr.save (); + cr.set_line_width (1); + Theme.color (cr, "Foreground 1"); + draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); + cr.stroke (); + cr.restore (); + } + + cr.save (); + + word = new Text (); + + width = this.width - padding; + x += padding; + scale = word.get_font_scale (); + y += font_size; + + // draw selection background + if (has_selection ()) { + tx = 0; + ty = 0; + + selection_start = get_selection_start (); + selection_stop = get_selection_stop (); + + cr.save (); + Theme.color (cr, "Highlighted 1"); + + for (int i = selection_start.paragraph; i <= selection_stop.paragraph; i++) { + return_if_fail (0 <= i < paragraphs.size); + Paragraph pg = paragraphs.get (i); + + if (pg.text_is_on_screen (allocation, widget_y)) { + int char_index = 0; + + foreach (Text next_word in pg.words) { + double cw = next_word.get_sidebearing_extent (); + bool paint_background = false; + bool partial_start = false; + bool partial_stop = false; + int wl; + + w = next_word.text; + wl = w.length; + scale = next_word.get_font_scale (); + + if (selection_start.paragraph == selection_stop.paragraph) { + partial_start = true; + partial_stop = true; + } else if (selection_start.paragraph < i < selection_stop.paragraph) { + paint_background = true; + } else if (selection_start.paragraph == i) { + paint_background = true; + partial_start = true; + } else if (selection_stop.paragraph == i) { + paint_background = char_index + wl < selection_stop.character_index; + partial_stop = !paint_background; + } + + if (paint_background && !(partial_start || partial_stop)) { + double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; + cr.rectangle (widget_x + padding + next_word.widget_x - 1, selection_y, cw + 1, font_size); + cr.fill (); + } + + if (partial_start || partial_stop) { + int index = char_index; + double bx = widget_x + padding + next_word.widget_x + (partial_start ? 0 : 1); + + next_word.iterate ((glyph, kerning, last) => { + double cwi; + int ci; + bool draw = (index >= selection_start.character_index && partial_start && !partial_stop) + || (index < selection_stop.character_index && !partial_start && partial_stop) + || (selection_start.character_index <= index < selection_stop.character_index && partial_start && partial_stop); + + cwi = (glyph.get_width ()) * next_word.get_font_scale () + kerning; + + if (draw) { + double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; + cr.rectangle (bx - 1, selection_y, cwi + 1, font_size); + cr.fill (); + } + + bx += cwi; + ci = ((!) glyph.get_unichar ().to_string ()).length; + index += ci; + }); + } + + char_index += w.length; + } + } + } + + cr.restore (); + } + + tx = 0; + ty = 0; + + int first_visible = 0; + int last_visible; + int paragraphs_size = paragraphs.size; + while (first_visible < paragraphs_size) { + if (paragraphs.get (first_visible).text_is_on_screen (allocation, widget_y)) { + break; + } + first_visible++; + } + + last_visible = first_visible; + while (last_visible < paragraphs_size) { + if (!paragraphs.get (last_visible).text_is_on_screen (allocation, widget_y)) { + last_visible++; + break; + } + last_visible++; + } + + if (paragraphs_size == 0) { + if (carret_is_visible) { + draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); + } + + return; + } + + Context cc; // cached context + Paragraph paragraph; + paragraph = paragraphs.get (0); + + tx = paragraph.start_x; + ty = paragraph.start_y; + + for (int i = first_visible; i < last_visible; i++) { + paragraph = paragraphs.get (i); + + tx = paragraph.start_x; + ty = paragraph.start_y; + + if (paragraph.cached_surface == null) { + paragraph.cached_surface = Screen.create_background_surface ((int) width + 2, paragraph.get_height () + (int) font_size + 2); + cc = new Context ((!) paragraph.cached_surface); + cc.scale (Screen.get_scale(), Screen.get_scale()); + + foreach (Text next_word in paragraph.words) { + if (next_word.text != "\n") { + next_word.draw_at_top (cc, next_word.widget_x, next_word.widget_y - ty); + } + } + } + + if (likely (paragraph.cached_surface != null)) { + // FIXME: subpixel offset in text area + Screen.paint_background_surface(cr, + (!) paragraph.cached_surface, + (int) (x + tx), + (int) (widget_y + paragraph.start_y - font_size + padding)); + } else { + warning ("No paragraph image."); + } + } + + if (carret_is_visible) { + get_carret_position (carret, out carret_x, out carret_y); + + if (carret_y < 0) { + draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); + } else { + draw_carret_at (cr, carret_x, carret_y); + } + } + + if (has_selection ()) { + get_carret_position (selection_end, out carret_x, out carret_y); + + if (carret_y < 0) { + draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); + } else { + draw_carret_at (cr, carret_x, carret_y); + } + } + } + + void get_carret_position (Carret carret, out double carret_x, out double carret_y) { + Paragraph paragraph; + double tx; + double ty; + int ch_index; + int wl; + double pos_x, pos_y; + + ch_index = 0; + + carret_x = -1; + carret_y = -1; + + return_if_fail (0 <= carret.paragraph < paragraphs.size); + paragraph = paragraphs.get (carret.paragraph); + + pos_x = -1; + pos_y = -1; + + foreach (Text next_word in paragraph.words) { + string w = next_word.text; + wl = w.length; + + if (carret.character_index == ch_index) { + pos_x = next_word.widget_x + widget_x + padding; + pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); + } else if (carret.character_index >= ch_index + wl) { + pos_x = next_word.widget_x + next_word.get_sidebearing_extent () + widget_x + padding; + pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); + + if (next_word.text.has_suffix ("\n")) { + pos_x = widget_x + padding; + pos_y += next_word.font_size; + } + } else if (ch_index < carret.character_index <= ch_index + wl) { + tx = widget_x + next_word.widget_x; + ty = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); + + if (carret.character_index <= ch_index) { + pos_x = widget_x + padding; + pos_y = ty; + } + + next_word.iterate ((glyph, kerning, last) => { + double cw; + int ci; + + cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; + ci = ((!) glyph.get_unichar ().to_string ()).length; + + if (ch_index < carret.character_index <= ch_index + ci) { + pos_x = tx + cw + padding; + pos_y = ty; + + if (glyph.get_unichar () == '\n') { + pos_x = widget_x + padding; + pos_y += next_word.font_size; + } + } + + tx += cw; + ch_index += ci; + }); + } + + ch_index += wl; + } + + carret_x = pos_x; + carret_y = pos_y; + } + + void draw_carret_at (Context cr, double x, double y) { + cr.save (); + cr.set_source_rgba (0, 0, 0, 0.5); + cr.set_line_width (1); + cr.move_to (x, y); + cr.line_to (x, y - font_size); + cr.stroke (); + cr.restore (); + } + + public void store_undo_edit_state () { + TextUndoItem ui = new TextUndoItem (carret); + ui.edited.add (get_current_paragraph ().copy ()); + undo_items.add (ui); + redo_items.clear (); + } + + public void redo () { + TextUndoItem i; + TextUndoItem undo_item; + + if (redo_items.size > 0) { + i = redo_items.get (redo_items.size - 1); + + undo_item = new TextUndoItem (i.carret); + + i.deleted.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pb.index - pa.index; + }); + + i.added.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pa.index - pb.index; + }); + + foreach (Paragraph p in i.deleted) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning ("Paragraph not found."); + } else { + undo_item.deleted.add (p.copy ()); + paragraphs.remove_at (p.index); + } + } + + foreach (Paragraph p in i.added) { + if (p.index == paragraphs.size) { + paragraphs.add (p.copy ()); + } else { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); + } else { + undo_item.added.add (paragraphs.get (p.index).copy ()); + paragraphs.insert (p.index, p.copy ()); + } + } + } + + foreach (Paragraph p in i.edited) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); + return; + } + + undo_item.edited.add (paragraphs.get (p.index).copy ()); + paragraphs.set (p.index, p.copy ()); + } + + redo_items.remove_at (redo_items.size - 1); + undo_items.add (undo_item); + + carret = i.carret.copy (); + layout (); + } + } + + public void undo () { + TextUndoItem i; + TextUndoItem redo_item; + + if (undo_items.size > 0) { + i = undo_items.get (undo_items.size - 1); + redo_item = new TextUndoItem (i.carret); + + i.deleted.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pa.index - pb.index; + }); + + i.added.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pb.index - pa.index; + }); + + foreach (Paragraph p in i.added) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning ("Paragraph not found."); + } else { + redo_item.added.add (paragraphs.get (p.index).copy ()); + paragraphs.remove_at (p.index); + } + } + + foreach (Paragraph p in i.deleted) { + if (p.index == paragraphs.size) { + paragraphs.add (p.copy ()); + } else { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); + } else { + redo_item.deleted.add (p.copy ()); + paragraphs.insert (p.index, p.copy ()); + } + } + } + + foreach (Paragraph p in i.edited) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); + return; + } + + redo_item.edited.add (paragraphs.get (p.index).copy ()); + paragraphs.set (p.index, p.copy ()); + } + + undo_items.remove_at (undo_items.size - 1); + redo_items.add (redo_item); + + carret = i.carret.copy (); + layout (); + } + } + + public void set_editable (bool editable) { + this.editable = editable; + } + + public class TextUndoItem : GLib.Object { + public Carret carret; + public Gee.ArrayList<Paragraph> added = new Gee.ArrayList<Paragraph> (); + public Gee.ArrayList<Paragraph> edited = new Gee.ArrayList<Paragraph> (); + public Gee.ArrayList<Paragraph> deleted = new Gee.ArrayList<Paragraph> (); + + public TextUndoItem (Carret c) { + carret = c.copy (); + } + } + + public class Paragraph : GLib.Object { + public double end_x = -10000; + public double end_y = -10000; + + public double start_x = -10000; + public double start_y = -10000; + + public double width = -10000; + public double text_area_width = -10000; + + public string text; + + public Gee.ArrayList<Text> words { + get { + if (words_in_paragraph.size == 0) { + generate_words (); + } + + return words_in_paragraph; + } + } + + private Gee.ArrayList<Text> words_in_paragraph = new Gee.ArrayList<Text> (); + public int text_length; + public bool need_layout = true; + public Surface? cached_surface = null; + double font_size; + public int index; + Color text_color; + + public Paragraph (string text, double font_size, int index, Color c) { + this.index = index; + this.font_size = font_size; + text_color = c; + set_text (text); + } + + public Paragraph copy () { + Paragraph p = new Paragraph (text.dup (), font_size, index, text_color); + p.need_layout = true; + return p; + } + + public bool is_empty () { + return text == ""; + } + + public void set_text (string t) { + this.text = t; + text_length = t.length; + need_layout = true; + words.clear (); + cached_surface = null; + } + + public int get_height () { + return (int) (end_y - start_y) + 1; + } + + public int get_width () { + return (int) width + 1; + } + + public bool text_is_on_screen (WidgetAllocation alloc, double widget_y) { + bool v = (0 <= start_y + widget_y <= alloc.height) + || (0 <= end_y + widget_y <= alloc.height) + || (start_y + widget_y <= 0 && alloc.height <= end_y + widget_y); + return v; + } + + private void generate_words () { + string w; + int p = 0; + bool carret_at_word_end = false; + Text word; + int carret = 0; + int iter_pos = 0; + + return_if_fail (words_in_paragraph.size == 0); + + while (p < text_length) { + w = get_next_word (out carret_at_word_end, ref iter_pos, carret); + + if (w == "") { + break; + } + + word = new Text (w, font_size, 0, text_color); + words_in_paragraph.add (word); + } + } + + string get_next_word (out bool carret_at_end_of_word, ref int iter_pos, int carret) { + int i; + int ni; + int pi; + string n; + int nl; + + carret_at_end_of_word = false; + + if (iter_pos >= text_length) { + carret_at_end_of_word = true; + return "".dup (); + } + + if (text.get_char (iter_pos) == '\n') { + iter_pos += "\n".length; + carret_at_end_of_word = (iter_pos == carret); + return "\n".dup (); + } + + i = text.index_of (" ", iter_pos); + pi = i + " ".length; + + ni = text.index_of ("\t", iter_pos); + if (ni != -1 && ni < pi || i == -1) { + i = ni; + pi = i + "\t".length; + } + + ni = text.index_of ("\n", iter_pos); + if (ni != -1 && ni < pi || i == -1) { + i = ni; + pi = i; + } + + if (iter_pos + iter_pos - pi > text_length || i == -1) { + n = text.substring (iter_pos); + } else { + n = text.substring (iter_pos, pi - iter_pos); + } + + nl = n.length; + if (iter_pos < carret < iter_pos + nl) { + n = text.substring (iter_pos, carret - iter_pos); + nl = n.length; + carret_at_end_of_word = true; + } + + iter_pos += nl; + + if (iter_pos == carret) { + carret_at_end_of_word = true; + } + + return n; + } + } + + public class Carret : GLib.Object { + + public int paragraph = 0; + + public int character_index { + get { + return ci; + } + + set { + ci = value; + } + } + + private int ci = 0; + + public double desired_x = 0; + public double desired_y = 0; + + public Carret () { + } + + public void print () { + stdout.printf (@"paragraph: $paragraph, character_index: $character_index\n"); + } + + public Carret copy () { + Carret c = new Carret (); + + c.paragraph = paragraph; + c.character_index = character_index; + + c.desired_x = desired_x; + c.desired_y = desired_y; + + return c; + } + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/fontconfig.c @@ -1,1 +1,142 @@ + /* + Copyright (C) 2015 Johan Mattsson + + 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 3 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. + */ + + #include <stdio.h> + #include <glib.h> + #include <fontconfig/fontconfig.h> + + gchar* find_font_with_property (FcConfig* fontconfig, const gchar* characters, const gchar* property) { + FcPattern* pattern; + FcCharSet* character_set; + FcObjectSet* font_properties; + FcFontSet* fonts; + FcPattern* font; + FcChar8* path; + gchar* result; + gchar* remaining_characters; + gunichar character; + + if (fontconfig == NULL) { + g_warning("Font config not loaded."); + return NULL; + } + + result = NULL; + pattern = FcPatternCreate (); + + character_set = FcCharSetCreate (); + + remaining_characters = (gchar*) characters; + while (TRUE) { + character = g_utf8_get_char (remaining_characters); + + if (character == '\0') { + break; + } + + FcCharSetAddChar(character_set, character); + + remaining_characters = g_utf8_next_char (remaining_characters); + } + + FcPatternAddCharSet (pattern, FC_CHARSET, character_set); + FcCharSetDestroy (character_set); + FcPatternAddInteger (pattern, FC_SLANT, FC_SLANT_ROMAN); + + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + font_properties = FcObjectSetBuild (property, NULL); + fonts = FcFontList (fontconfig, pattern, font_properties); + + if (fonts && fonts->nfont > 0) { + font = fonts->fonts[0]; + if (FcPatternGetString(font, property, 0, &path) == FcResultMatch) { + result = g_strdup ((gchar*) path); + } + } + + if (fonts) { + FcFontSetDestroy(fonts); + } + + if (pattern) { + FcPatternDestroy(pattern); + } + + return result; + } + + /** Find a fallback font for a set of characters. + * @return A path to the font file. + */ + gchar* find_font (FcConfig* fontconfig, const gchar* characters) { + return find_font_with_property (fontconfig, characters, FC_FILE); + } + + /** Find a fallback font for a set of characters. + * @return Family name of the font. + */ + gchar* find_font_family (FcConfig* fontconfig, const gchar* characters) { + return find_font_with_property (fontconfig, characters, FC_FAMILY); + } + + /** Find a font file from its family name. + * @param font_config fontconfig instance + * @param font_name name of the font + * @return full path to the font file + */ + gchar* find_font_file (FcConfig* font_config, const gchar* font_name) { + const FcChar8* name; + FcPattern* search_pattern; + FcPattern* font; + FcChar8* file; + gchar* path; + FcObjectSet* font_properties; + FcFontSet* fonts; + int i; + + if (font_config == NULL) { + g_warning("Font config not loaded."); + return NULL; + } + + path = NULL; + name = font_name; + + search_pattern = FcPatternCreate (); + FcPatternAddString (search_pattern, FC_FAMILY, name); + FcPatternAddBool (search_pattern, FC_SCALABLE, FcTrue); + FcPatternAddInteger (search_pattern, FC_WEIGHT, FC_WEIGHT_MEDIUM); + FcPatternAddInteger (search_pattern, FC_SLANT, FC_SLANT_ROMAN); + + font_properties = FcObjectSetBuild (FC_FILE, NULL); + fonts = FcFontList (font_config, search_pattern, font_properties); + + if (fonts->nfont > 0) { + for (i = 0; i < fonts->nfont; i++) { + font = fonts->fonts[i]; + + if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { + path = g_strdup ((gchar*) file); + break; + } + + FcPatternDestroy (font); + } + } + + FcPatternDestroy (search_pattern); + + return path; + }
--- a/libbirdfont/Theme.vala +++ b/libbirdfont/Theme.vala @@ -103,8 +103,8 @@ Theme.set_default_color ("Stroke Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Handle Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Fill Color", 0.5, 0.5, 0.5, 1); - Theme.set_default_color ("Selected Objects", 35 / 255.0, 131 / 255.0, 194 / 255.0, 1); - + Theme.set_default_color ("Objects", 0, 0, 0, 1); + Theme.set_default_color ("Background 1", 1, 1, 1, 1); Theme.set_default_color ("Dialog Background", 238 / 255.0, 239 / 255.0, 243 / 255.0, 1); Theme.set_default_color ("Menu Background", 222 / 255.0, 221 / 255.0, 226 / 255.0, 1); @@ -209,7 +209,7 @@ Theme.set_default_color ("Stroke Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Handle Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Fill Color", 0.5, 0.5, 0.5, 1); - Theme.set_default_color ("Selected Objects", 35 / 255.0, 131 / 255.0, 194 / 255.0, 1); + Theme.set_default_color ("Objects", 0, 0, 0, 1); Theme.set_default_color ("Background 1", 1, 1, 1, 1); Theme.set_default_color ("Dialog Background", 55 / 255.0, 56 / 255.0, 62 / 255.0, 1); @@ -314,7 +314,7 @@ Theme.set_default_color ("Stroke Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Handle Color", 141 / 255.0, 141 / 255.0, 141 / 255.0, 1); Theme.set_default_color ("Fill Color", 0, 0, 0, 1); - Theme.set_default_color ("Selected Objects", 0, 0, 0, 1); + Theme.set_default_color ("Objects", 0, 0, 0, 1); Theme.set_default_color ("Background 1", 1, 1, 1, 1); Theme.set_default_color ("Dialog Background", 1, 1, 1, 1); @@ -422,6 +422,7 @@ t_("Handle Color"); t_("Fill Color"); t_("Selected Objects"); + t_("Objects"); t_("Background 1"); t_("Dialog Background");
--- a/libbirdfont/Tool.vala +++ b/libbirdfont/Tool.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012, 2014 Johan Mattsson + Copyright (C) 2012 2014 2016 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -66,6 +66,8 @@ bool show_bg = true; public string tip = ""; + string extented_description = ""; + TextArea? help = null; public bool persistent = false; public bool editor_events = false; @@ -82,8 +84,9 @@ public signal void redraw_tool (); /** Create tool with a certain name and load icon "name".png */ - public Tool (string? name = null, string tip = "") { + public Tool (string? name = null, string tip = "", string extended_description = "") { this.tip = tip; + this.extented_description = extended_description; icon_font = new Text (); @@ -126,6 +129,10 @@ } return false; }); + } + + public virtual void clear_cache () { + help = null; } public virtual string get_tip () { @@ -221,6 +228,10 @@ } public string get_key_binding () { + if (is_null (MainWindow.get_menu ())) { + return ""; + } + ToolItem? ti = MainWindow.get_menu ().get_item_for_tool (this); ToolItem t;
--- a/libbirdfont/ToolItem.vala +++ b/libbirdfont/ToolItem.vala @@ -18,6 +18,26 @@ public Tool tool; + public override uint modifiers { + get { + return base.modifiers; + } + + set { + base.modifiers = value; + } + } + + public override unichar key { + get { + return base.key; + } + + set { + base.key = value; + } + } + public ToolItem (Tool tool) { base (tool.tip, tool.name);
--- a/libbirdfont/TrackTool.vala +++ b/libbirdfont/TrackTool.vala @@ -109,7 +109,6 @@ end_point.path.reverse (); } - Path path = end_point.path; glyph.set_active_path (end_point.path); } else { p = new Path (); @@ -125,8 +124,11 @@ start_update_timer (); drawing = true; - foreach (Path path in glyph.active_paths) { - path.create_full_stroke (); // cache merged stroke parts + foreach (Object path in glyph.active_paths) { + if (path is PathObject) { + // cache merged stroke parts + ((PathObject) path).get_path ().create_full_stroke (); + } } } }); @@ -150,8 +152,9 @@ g = MainWindow.get_current_glyph (); if (g.active_paths.size > 0) { // set type for last point - p = g.active_paths.get (g.active_paths.size - 1); - + Object o = g.active_paths.get (g.active_paths.size - 1); + p = ((PathObject) o).get_path (); + if (p.points.size > 1) { previous = p.points.get (p.points.size - 1); previous.type = PointType.CUBIC; @@ -167,10 +170,13 @@ return_if_fail (drawing); add_endpoint_and_merge (x, y); } - - foreach (Path path in g.active_paths) { - convert_hidden_points (path); - path.update_region_boundaries (); + + foreach (Object path in g.active_paths) { + if (path is PathObject) { + Path freehand_path = ((PathObject) path).get_path (); + convert_hidden_points (freehand_path); + freehand_path.update_region_boundaries (); + } } g.clear_active_paths (); @@ -231,12 +237,17 @@ } } } - - // FIXME: double check + void set_tie () { Glyph glyph = MainWindow.get_current_glyph (); var paths = glyph.get_visible_paths (); - Path p = paths.get (paths.size - 1); + Path p; + + if (paths.size == 0) { + return; + } + + p = paths.get (paths.size - 1); foreach (EditPoint ep in p.points) { if (ep.get_right_handle ().is_line () || ep.get_left_handle ().is_line ()) { @@ -405,7 +416,14 @@ return; } - p = glyph.active_paths.get (glyph.active_paths.size - 1); + Object o = glyph.active_paths.get (glyph.active_paths.size - 1); + + if (unlikely (!(o is PathObject))) { + warning ("Object is not a path"); + return; + } + + p = ((PathObject) o).get_path (); p.reopen (); EditPoint last_point = new EditPoint (); @@ -422,10 +440,11 @@ added_points++; PenTool.convert_point_to_line (new_point, false); + new_point.set_point_type (PointType.HIDDEN); p.recalculate_linear_handles_for_point (new_point); - last_point.get_right_handle ().length = 0.000001; + last_point.get_right_handle ().length = 0.000001; if (p.points.size > 1) { glyph.redraw_segment (new_point, new_point.get_prev ()); @@ -501,7 +520,15 @@ return new Path (); } - return glyph.active_paths.get (glyph.active_paths.size - 1); + Object o = glyph.active_paths.get (glyph.active_paths.size - 1); + + if (likely (o is PathObject)) { + return ((PathObject) o).get_path (); + } + + warning ("Active object is a path."); + + return new Path (); } /** Delete all points close to the pixel at x,y. */ @@ -530,10 +557,6 @@ * @return the last removed point. */ public void convert_points_to_line () { - EditPoint ep, last_point; - double sum_x, sum_y, nx, ny; - int px, py; - EditPoint average, previous; Path p; Glyph glyph; Gee.ArrayList<EditPoint> points; @@ -557,9 +580,6 @@ warning ("Missing point."); return; } - - sum_x = 0; - sum_y = 0; int start = p.points.size - 1 - added_points; int stop = p.points.size - 1;
--- /dev/null +++ b/libbirdfont/TransformTask.vala @@ -1,1 +1,74 @@ + /* + Copyright (C) 2016 Johan Mattsson + + 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 3 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. + */ + + namespace BirdFont { + + public class TransformTask : Task { + static Transform transform; + + public TransformTask (Transform transform) { + base (process_transform); + TransformTask.transform = transform; + } + + public static void process_transform () { + OverView o; + Glyph g; + OverView.OverViewUndoItem ui; + + o = OverviewTools.get_overview (); + ui = new OverView.OverViewUndoItem (); + + Font f = BirdFont.get_current_font (); + ui.alternate_sets = f.alternates.copy (); + + foreach (GlyphCollection gc in o.selected_items) { + if (gc.length () > 0) { + g = gc.get_current (); + ui.glyphs.add (gc.copy_deep ()); + g.add_help_lines (); + + if (transform == Transform.SLANT) { + if (OverviewTools.skew.get_value () != 0) { + DrawingTools.resize_tool.skew_glyph (g, -OverviewTools.skew.get_value (), 0, false); + } + } + + if (transform == Transform.SIZE) { + if (OverviewTools.resize.get_value () != 100) { + double scale = OverviewTools.resize.get_value () / 100; + DrawingTools.resize_tool.resize_glyph (g, scale, scale, false); + } + } + + if (transform == Transform.SVG_TO_TTF) { + DrawingTools.move_tool.convert_glyph_to_monochrome (gc.get_current ()); + } + } + } + + foreach (OverViewItem item in o.visible_items) { + item.clear_cache (); + item.draw_glyph_from_font (); + } + + o.undo_items.add (ui); + + MainWindow.get_overview ().update_item_list (); + GlyphCanvas.redraw (); + } + } + + }
--- a/libbirdgems/fit_cubic.c +++ b/libbirdgems/fit_cubic.c @@ -22,10 +22,10 @@ #include "GraphicsGems.h" - #ifdef MAC - #include <malloc/malloc.h> - #else + #ifdef __linux__ #include <malloc.h> + #else + #include <stdlib.h> #endif #include <math.h>
--- /dev/null +++ b/libsvgbird/AttributePattern.vala @@ -1,1 +1,144 @@ + /* + Copyright (C) 2016 Johan Mattsson + + 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 3 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. + */ + + using B; + using Math; + + namespace SvgBird { + + public enum AttributePatternType { + NONE, + ANYTHING, + LIST, + EQUALS, + STARTS_WITH + } + + public class AttributePattern : GLib.Object { + public string name = ""; + public string? content = null; + public AttributePatternType type = AttributePatternType.NONE; + + public AttributePattern copy () { + AttributePattern a = new AttributePattern (); + a.name = name; + a.content = content; + a.type = type; + return a; + } + + public bool match (Attributes attributes) { + switch (type) { + case AttributePatternType.ANYTHING: + return match_attribute_name (attributes); + case AttributePatternType.LIST: + return match_list (attributes); + case AttributePatternType.EQUALS: + return attribute_equals (attributes); + case AttributePatternType.STARTS_WITH: + return attribute_start_with (attributes); + } + + return false; + } + + string remove_hypen (string content) { + int hyphen = content.index_of ("-"); + + if (hyphen == -1) { + return content; + } + + return content.substring (0, hyphen); + } + + bool attribute_start_with (Attributes attributes) { + foreach (Attribute attribute in attributes) { + if (attribute.get_name () == name + && remove_hypen (attribute.get_content ()) == ((!) content)) { + return true; + } + } + + return false; + } + + bool attribute_equals (Attributes attributes) { + foreach (Attribute attribute in attributes) { + if (attribute.get_name () == name + && attribute.get_content () == ((!) content)) { + return true; + } + } + + return false; + } + + bool match_attribute_name (Attributes attributes) { + foreach (Attribute attribute in attributes) { + if (attribute.get_name () == name) { + return true; + } + } + + return false; + } + + bool match_list (Attributes attributes) { + if (content == null) { + return false; + } + + string[] list = ((!) content).split (" "); + foreach (Attribute attribute in attributes) { + if (attribute.get_name () == name) { + + string attribute_content = attribute.get_content (); + foreach (string list_item in list) { + if (attribute_content == list_item) { + return true; + } + } + } + } + + return false; + } + + public string to_string () { + string c; + + if (content == null) { + c = "null"; + } else { + c = (!) content; + } + + switch (type) { + case AttributePatternType.ANYTHING: + return "[" + name + "]"; + case AttributePatternType.LIST: + return "[" + name + "~=" + c + "]"; + case AttributePatternType.EQUALS: + return "[" + name + "=" + c + "]"; + case AttributePatternType.STARTS_WITH: + return "[" + name + "|=" + c + "]"; + } + + return "No attributes."; + } + } + + }
--- /dev/null +++ b/libsvgbird/BezierPoints.vala @@ -1,1 +1,46 @@ + /* + Copyright (C) 2014 2016 Johan Mattsson + + 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 3 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. + */ + + namespace SvgBird { + + /** Bezier point container for the SVG parser. */ + public class BezierPoints { + public unichar type = '\0'; + public unichar svg_type = '\0'; + public double x0 = 0; + public double y0 = 0; + public double x1 = 0; + public double y1 = 0; + public double x2 = 0; + public double y2 = 0; + + // arc arguments + public double rx = 0; + public double ry = 0; + public double rotation = 0; + public bool large_arc = false; + public bool sweep = false; + // the arc instructions begins at x0, y0 and ends at x1, x1 + + public string to_string () { + if (svg_type == 'A' || svg_type == 'a') { + return @"SVG type:$((!) svg_type.to_string ()) $x0,$y0 $x1,$y1 rx=$rx, ry=$ry, rotation=$rotation, large_arc=$large_arc, sweep=$sweep)"; + } + + return @"$((!)type.to_string ()) $x0,$y0 $x1,$y1 $x2,$y2 SVG:$((!)svg_type.to_string ())"; + } + } + + }
diff --git libsvgbird/Circle.vala(new)
--- /dev/null +++ b/libsvgbird/Circle.vala @@ -1,1 +1,61 @@ + /* + Copyright (C) 2016 Johan Mattsson + + 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 3 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. + */ + + using Cairo; + + namespace SvgBird { + + public class Circle : Object { + + public double cx = 0; + public double cy = 0; + public double r = 0; + + public Circle () { + } + + public override bool is_over (double x, double y) { + to_object_view (ref x, ref y); + double dx = x - cx; + double dy = y - cy; + return Math.sqrt (dx * dx + dy * dy) <= r; + } + + public override void draw_outline (Context cr) { + cr.move_to (cx + r, cy); + cr.arc (cx, cy, r, 0, 2 * Math.PI); + } + + public override bool is_empty () { + return false; + } + + public override Object copy () { + Circle c = new Circle (); + + Object.copy_attributes (this, c); + c.cx = cx; + c.cy = cy; + c.r = r; + + return c; + } + + public override string to_string () { + return "Circle"; + } + } + + }
diff --git libsvgbird/ClipPath.vala(new)
--- /dev/null +++ b/libsvgbird/ClipPath.vala @@ -1,1 +1,44 @@ + /* + Copyright (C) 2016 Johan Mattsson + + 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 3 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. + */ + + using Cairo; + + namespace SvgBird { + + public class ClipPath : GLib.Object { + Layer layer = new Layer (); + + public string id { + get { + return layer.id; + } + } + + public ClipPath (Layer layer) { + this.layer = layer; + } + + public void apply (Context cr) { + layer.draw_outline (cr); + cr.clip (); + } + + public ClipPath copy () { + ClipPath path = new ClipPath (layer); + return path; + } + } + + }
diff --git libsvgbird/Color.vala(new)
--- /dev/null +++ b/libsvgbird/Color.vala @@ -1,1 +1,437 @@ + /* Copyright (C) 1999 The Free Software Foundation + * + * Authors: Simon Budig <Simon.Budig@unix-ag.org> (original code) + * Federico Mena-Quintero <federico@gimp.org> (cleanup for GTK+) + * Jonathan Blandford <jrb@redhat.com> (cleanup for GTK+) + * Johan Mattsson (adapted to BirdFont) + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + namespace SvgBird { + + public class Color { + public double r; + public double g; + public double b; + public double a; + + public Color (double r, double g, double b, double a) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public Color.create_copy (SvgBird.Color color) { + this.r = color.r; + this.g = color.g; + this.b = color.b; + this.a = color.a; + } + + public Color.hsva (double h, double s, double v, double a) { + double hue, saturation, value; + double f, p, q, t; + double r, g, b; + + if (s == 0.0) { + r = v; + g = v; + b = v; + } else { + hue = h * 6.0; + saturation = s; + value = v; + + if (hue == 6.0) { + hue = 0.0; + } + + f = hue - (int) hue; + p = value * (1.0 - saturation); + q = value * (1.0 - saturation * f); + t = value * (1.0 - saturation * (1.0 - f)); + + switch ((int) hue) { + case 0: + r = value; + g = t; + b = p; + break; + + case 1: + r = q; + g = value; + b = p; + break; + + case 2: + r = p; + g = value; + b = t; + break; + + case 3: + r = p; + g = q; + b = value; + break; + + case 4: + r = t; + g = p; + b = value; + break; + + case 5: + r = value; + g = p; + b = q; + break; + + default: + assert_not_reached (); + } + } + + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public void to_hsva (out double h, out double s, out double v, out double a) { + double red, green, blue; + double min, max; + double delta; + + a = this.a; + + red = r; + green = g; + blue = b; + + h = 0.0; + + if (red > green) { + if (red > blue) + max = red; + else + max = blue; + + if (green < blue) + min = green; + else + min = blue; + } else { + if (green > blue) + max = green; + else + max = blue; + + if (red < blue) + min = red; + else + min = blue; + } + + v = max; + + if (max != 0.0) + s = (max - min) / max; + else + s = 0.0; + + if (s == 0.0) + h = 0.0; + else { + delta = max - min; + + if (red == max) + h = (green - blue) / delta; + else if (green == max) + h = 2 + (blue - red) / delta; + else if (blue == max) + h = 4 + (red - green) / delta; + + h /= 6.0; + + if (h < 0.0) + h += 1.0; + else if (h > 1.0) + h -= 1.0; + } + } + + public static Color? parse (string? svg_color) { + if (svg_color == null) { + return null; + } + + string color = (!) svg_color; + uint32 c; + string[] arguments; + Color parsed = new Color (0, 0, 0, 1); + + if (color == "none") { + return null; + } + + if (!color.has_prefix ("#")) { + color = get_hex_for_name (color); + } + + color = color.replace ("#", ""); + + if (color.char_count () == 6) { + color.scanf ("%x", out c); + parsed.r = (uint8)((c & 0xFF0000) >> 16) / 254.0; + parsed.g = (uint8)((c & 0x00FF00) >> 8)/ 254.0; + parsed.b = (uint8)(c & 0x0000FF) / 254.0; + } else if (color.char_count () == 3) { + color.scanf ("%x", out c); + parsed.r = (uint8)(((c & 0xF00) >> 4) | ((c & 0xF00) >> 8)) / 254.0; + parsed.g = (uint8)((c & 0x0F0) | ((c & 0x0F0) >> 4)) / 254.0; + parsed.b = (uint8)(((c & 0x00F) << 4) | (c & 0x00F)) / 254.0; + } else if (color.index_of ("%") > -1) { + color = color.replace ("rgb", ""); + color = color.replace (" ", ""); + color = color.replace ("\t", ""); + color = color.replace ("%", ""); + arguments = color.split (","); + + return_val_if_fail (arguments.length == 3, parsed); + arguments[0].scanf ("%lf", out parsed.r); + arguments[1].scanf ("%lf", out parsed.g); + arguments[2].scanf ("%lf", out parsed.b); + } else if (color.index_of ("rgb") > -1) { + color = color.replace ("rgb", ""); + color = color.replace (" ", ""); + color = color.replace ("\t", ""); + arguments = color.split (","); + + return_val_if_fail (arguments.length == 3, parsed); + + int r, g, b; + arguments[0].scanf ("%d", out r); + parsed.r = r / 254.0; + + arguments[1].scanf ("%d", out g); + parsed.g = g / 254.0; + + arguments[2].scanf ("%d", out b); + parsed.b = b / 254.0; + } else { + warning ("Unknown color type: " + color); + } + + return parsed; + } + + public string to_rgb_hex () { + StringBuilder rgb = new StringBuilder (); + rgb.append ("#"); + rgb.append_printf ("%02x", (int) Math.rint (r * 254)); + rgb.append_printf ("%02x", (int) Math.rint (g * 254)); + rgb.append_printf ("%02x", (int) Math.rint (b * 254)); + return rgb.str; + } + + public string to_string () { + StringBuilder rgba = new StringBuilder (); + rgba.append (to_rgb_hex ()); + rgba.append_printf ("%x", (int) Math.rint (a * 254)); + return rgba.str; + } + + public Color copy () { + return new Color (r, g, b, a); + } + + public static string get_hex_for_name (string name) { + string color = name.down (); + + if (color == "black") return "#000000"; + if (color == "silver") return "#C0C0C0"; + if (color == "gray") return "#808080"; + if (color == "maroon") return "#800000"; + if (color == "red") return "#FF0000"; + if (color == "purple") return "#800080"; + if (color == "white") return "#FFFFFF"; + if (color == "fuchsia") return "#FF00FF"; + if (color == "green") return "#008000"; + if (color == "lime") return "#00FF00"; + if (color == "olive") return "#808000"; + if (color == "yellow") return "#FFFF00"; + if (color == "navy") return "#000080"; + if (color == "blue") return "#0000FF"; + if (color == "teal") return "#008080"; + if (color == "aqua") return "#00FFFF"; + if (color == "aliceblue") return "#f0f8ff"; + if (color == "antiquewhite") return "#faebd7"; + if (color == "aqua") return "#00ffff"; + if (color == "aquamarine") return "#7fffd4"; + if (color == "azure") return "#f0ffff"; + if (color == "beige") return "#f5f5dc"; + if (color == "bisque") return "#ffe4c4"; + if (color == "black") return "#000000"; + if (color == "blanchedalmond") return "#ffebcd"; + if (color == "blue") return "#0000ff"; + if (color == "blueviolet") return "#8a2be2"; + if (color == "brown") return "#a52a2a"; + if (color == "burlywood") return "#deb887"; + if (color == "cadetblue") return "#5f9ea0"; + if (color == "chartreuse") return "#7fff00"; + if (color == "chocolate") return "#d2691e"; + if (color == "coral") return "#ff7f50"; + if (color == "cornflowerblue") return "#6495ed"; + if (color == "cornsilk") return "#fff8dc"; + if (color == "crimson") return "#dc143c"; + if (color == "cyan") return "#00ffff"; + if (color == "darkblue") return "#00008b"; + if (color == "darkcyan") return "#008b8b"; + if (color == "darkgoldenrod") return "#b8860b"; + if (color == "darkgray") return "#a9a9a9"; + if (color == "darkgreen") return "#006400"; + if (color == "darkgrey") return "#a9a9a9"; + if (color == "darkkhaki") return "#bdb76b"; + if (color == "darkmagenta") return "#8b008b"; + if (color == "darkolivegreen") return "#556b2f"; + if (color == "darkorange") return "#ff8c00"; + if (color == "darkorchid") return "#9932cc"; + if (color == "darkred") return "#8b0000"; + if (color == "darksalmon") return "#e9967a"; + if (color == "darkseagreen") return "#8fbc8f"; + if (color == "darkslateblue") return "#483d8b"; + if (color == "darkslategray") return "#2f4f4f"; + if (color == "darkslategrey") return "#2f4f4f"; + if (color == "darkturquoise") return "#00ced1"; + if (color == "darkviolet") return "#9400d3"; + if (color == "deeppink") return "#ff1493"; + if (color == "deepskyblue") return "#00bfff"; + if (color == "dimgray") return "#696969"; + if (color == "dimgrey") return "#696969"; + if (color == "dodgerblue") return "#1e90ff"; + if (color == "firebrick") return "#b22222"; + if (color == "floralwhite") return "#fffaf0"; + if (color == "forestgreen") return "#228b22"; + if (color == "fuchsia") return "#ff00ff"; + if (color == "gainsboro") return "#dcdcdc"; + if (color == "ghostwhite") return "#f8f8ff"; + if (color == "gold") return "#ffd700"; + if (color == "goldenrod") return "#daa520"; + if (color == "gray") return "#808080"; + if (color == "green") return "#008000"; + if (color == "greenyellow") return "#adff2f"; + if (color == "grey") return "#808080"; + if (color == "honeydew") return "#f0fff0"; + if (color == "hotpink") return "#ff69b4"; + if (color == "indianred") return "#cd5c5c"; + if (color == "indigo") return "#4b0082"; + if (color == "ivory") return "#fffff0"; + if (color == "khaki") return "#f0e68c"; + if (color == "lavender") return "#e6e6fa"; + if (color == "lavenderblush") return "#fff0f5"; + if (color == "lawngreen") return "#7cfc00"; + if (color == "lemonchiffon") return "#fffacd"; + if (color == "lightblue") return "#add8e6"; + if (color == "lightcoral") return "#f08080"; + if (color == "lightcyan") return "#e0ffff"; + if (color == "lightgoldenrodyellow") return "#fafad2"; + if (color == "lightgray") return "#d3d3d3"; + if (color == "lightgreen") return "#90ee90"; + if (color == "lightgrey") return "#d3d3d3"; + if (color == "lightpink") return "#ffb6c1"; + if (color == "lightsalmon") return "#ffa07a"; + if (color == "lightseagreen") return "#20b2aa"; + if (color == "lightskyblue") return "#87cefa"; + if (color == "lightslategray") return "#778899"; + if (color == "lightslategrey") return "#778899"; + if (color == "lightsteelblue") return "#b0c4de"; + if (color == "lightyellow") return "#ffffe0"; + if (color == "lime") return "#00ff00"; + if (color == "limegreen") return "#32cd32"; + if (color == "linen") return "#faf0e6"; + if (color == "magenta") return "#ff00ff"; + if (color == "maroon") return "#800000"; + if (color == "mediumaquamarine") return "#66cdaa"; + if (color == "mediumblue") return "#0000cd"; + if (color == "mediumorchid") return "#ba55d3"; + if (color == "mediumpurple") return "#9370db"; + if (color == "mediumseagreen") return "#3cb371"; + if (color == "mediumslateblue") return "#7b68ee"; + if (color == "mediumspringgreen") return "#00fa9a"; + if (color == "mediumturquoise") return "#48d1cc"; + if (color == "mediumvioletred") return "#c71585"; + if (color == "midnightblue") return "#191970"; + if (color == "mintcream") return "#f5fffa"; + if (color == "mistyrose") return "#ffe4e1"; + if (color == "moccasin") return "#ffe4b5"; + if (color == "navajowhite") return "#ffdead"; + if (color == "navy") return "#000080"; + if (color == "oldlace") return "#fdf5e6"; + if (color == "olive") return "#808000"; + if (color == "olivedrab") return "#6b8e23"; + if (color == "orange") return "#ffa500"; + if (color == "orangered") return "#ff4500"; + if (color == "orchid") return "#da70d6"; + if (color == "palegoldenrod") return "#eee8aa"; + if (color == "palegreen") return "#98fb98"; + if (color == "paleturquoise") return "#afeeee"; + if (color == "palevioletred") return "#db7093"; + if (color == "papayawhip") return "#ffefd5"; + if (color == "peachpuff") return "#ffdab9"; + if (color == "peru") return "#cd853f"; + if (color == "pink") return "#ffc0cb"; + if (color == "plum") return "#dda0dd"; + if (color == "powderblue") return "#b0e0e6"; + if (color == "purple") return "#800080"; + if (color == "red") return "#ff0000"; + if (color == "rosybrown") return "#bc8f8f"; + if (color == "royalblue") return "#4169e1"; + if (color == "saddlebrown") return "#8b4513"; + if (color == "salmon") return "#fa8072"; + if (color == "sandybrown") return "#f4a460"; + if (color == "seagreen") return "#2e8b57"; + if (color == "seashell") return "#fff5ee"; + if (color == "sienna") return "#a0522d"; + if (color == "silver") return "#c0c0c0"; + if (color == "skyblue") return "#87ceeb"; + if (color == "slateblue") return "#6a5acd"; + if (color == "slategray") return "#708090"; + if (color == "slategrey") return "#708090"; + if (color == "snow") return "#fffafa"; + if (color == "springgreen") return "#00ff7f"; + if (color == "steelblue") return "#4682b4"; + if (color == "tan") return "#d2b48c"; + if (color == "teal") return "#008080"; + if (color == "thistle") return "#d8bfd8"; + if (color == "tomato") return "#ff6347"; + if (color == "turquoise") return "#40e0d0"; + if (color == "violet") return "#ee82ee"; + if (color == "wheat") return "#f5deb3"; + if (color == "white") return "#ffffff"; + if (color == "whitesmoke") return "#f5f5f5"; + if (color == "yellow") return "#ffff00"; + if (color == "yellowgreen") return "#9acd32"; + + return "#000000"; + } + } + + }
diff --git libsvgbird/Defs.vala(new)
--- /dev/null +++ b/libsvgbird/Defs.vala @@ -1,1 +1,166 @@ + /* + Copyright (C) 2016 Johan Mattsson + + 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 3 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. + */ + + using B; + using Math; + + namespace SvgBird { + + public class Defs { + public Gee.ArrayList<ClipPath> clip_paths = new Gee.ArrayList<ClipPath> (); + public Gee.ArrayList<Gradient> gradients = new Gee.ArrayList<Gradient> (); + public Gee.ArrayList<RadialGradient> radial_gradients = new Gee.ArrayList<RadialGradient> (); + public Gee.ArrayList<LinearGradient> linear_gradients = new Gee.ArrayList<LinearGradient> (); + public StyleSheet style_sheet = new StyleSheet (); + + public void add_linear_gradient (LinearGradient g) { + gradients.add (g); + linear_gradients.add (g); + } + + public void add_radial_gradient (RadialGradient g) { + gradients.add (g); + radial_gradients.add (g); + } + + public ClipPath? get_clip_path_for_url (string? url) { + if (url == null) { + return null; + } + + string tag_id = get_id_from_url ((!) url); + return get_clip_path_for_id (tag_id); + } + + public ClipPath? get_clip_path_for_id (string id) { + string tag_id; + + if (id.has_prefix ("#")) { + tag_id = id.substring ("#".length); + } else { + tag_id = id; + } + + foreach (ClipPath clip_path in clip_paths) { + if (clip_path.id == tag_id) { + return clip_path; + } + } + + return null; + } + + public static string get_id_from_url (string url) { + if (unlikely (!is_url (url))) { + return ""; + } + + int p1 = url.index_of ("("); + if (unlikely (p1 == -1)) { + warning ("Not an URL: " + url); + return ""; + } + + int p2 = url.index_of (")"); + if (unlikely (p2 == -1 || p2 < p1)) { + warning ("Not an URL: " + url); + return ""; + } + + p1 += "(".length; + int length = p2 - p1; + return url.substring (p1, length); + } + + public Gradient? get_gradient_for_url (string? url) { + if (url == null) { + return null; + } + + string tag_id = get_id_from_url ((!) url); + return get_gradient_for_id (tag_id); + } + + public Gradient? get_gradient_for_id (string id) { + string tag_id; + + if (id.has_prefix ("#")) { + tag_id = id.substring ("#".length); + } else { + tag_id = id; + } + + tag_id = tag_id.down (); + + foreach (Gradient gradient in gradients) { + if (gradient.id.down () == tag_id) { + return gradient; + } + } + + return null; + } + + public static bool is_url (string? attribute) { + if (attribute == null) { + return false; + } + + return ((!) attribute).has_prefix ("url"); + } + + public Defs shallow_copy () { + Defs d = new Defs (); + + foreach (Gradient g in gradients) { + d.gradients.add (g); + } + + foreach (RadialGradient g in radial_gradients) { + d.radial_gradients.add (g); + } + + foreach (LinearGradient g in linear_gradients) { + d.linear_gradients.add (g); + } + + d.style_sheet = style_sheet.shallow_copy (); + + return d; + } + + public Defs copy () { + Defs d = new Defs (); + + foreach (Gradient g in gradients) { + Gradient gradient_copy = g.copy (); + d.gradients.add (gradient_copy); + + if (gradient_copy is LinearGradient) { + d.linear_gradients.add ((LinearGradient) gradient_copy); + } + + if (gradient_copy is RadialGradient) { + d.radial_gradients.add ((RadialGradient) gradient_copy); + } + } + + d.style_sheet = style_sheet.copy (); + + return d; + } + } + + }
diff --git libsvgbird/Doubles.vala(new)
--- /dev/null +++ b/libsvgbird/Doubles.vala @@ -1,1 +1,230 @@ + /* + Copyright (C) 2015 Johan Mattsson + + 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 3 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. + */ + + using Cairo; + + namespace SvgBird { + + public class Doubles : GLib.Object { + public PointValue* data; + public int size = 0; + int capacity = 10; + + public Doubles () { + data = new PointValue[capacity]; + } + + public Doubles.for_capacity (int capacity) { + data = new PointValue[capacity]; + this.capacity = capacity; + } + + ~Doubles () { + delete data; + data = null; + } + + public void clear () { + size = 0; + } + + public void set_double (int index, double p) { + if (unlikely (index < 0)) { + warning ("index < 0"); + return; + } + + if (unlikely (index >= size)) { + warning ("index >= size"); + return; + } + + data[index].value = p; + } + + public void set_type (int index, uint32 t) { + if (unlikely (index < 0)) { + warning ("index < 0"); + return; + } + + if (unlikely (index >= size)) { + warning ("index >= size"); + return; + } + + data[index].type = t; + } + + public void insert (int index, double p) { + insert_element (index); + data[index].value = p; + } + + public void insert_type (int index, uint32 p) { + insert_element (index); + data[index].type = p; + } + + public void insert_element (int index) { + if (capacity < size + 1) { + increase_capacity (); + } + + if (unlikely (index < 0 || index > size)) { + warning (@"Bad index $index."); + return; + } + + PointValue* point_data = new PointValue[capacity]; + + if (index > 0) { + Posix.memcpy (point_data, data, sizeof (PointValue) * index); + } + + if (index < size) { + int dest_position = index + 1; + Posix.memcpy (point_data + dest_position, data + index, sizeof (PointValue) * (size - index)); + } + + size += 1; + delete data; + data = point_data; + } + + public void remove_first (int n) { + if (size < n) { + return; + } + + size -= n; + + for (int i = 0; i < size; i++) { + data[i] = data[i + n]; + } + } + + public void remove (int offset, int length) { + if (unlikely (offset < 0 || offset + length > size)) { + warning (@"Invalid offset: $offset, length: $length, size: $size"); + return; + } + + for (int i = offset; i < size; i++) { + data[i] = data[i + length]; + } + + size -= length; + } + + void increase_capacity () { + int new_capacity = 2 * capacity; + PointValue* new_data = new PointValue[new_capacity]; + Posix.memcpy (new_data, data, sizeof (PointValue) * size); + delete data; + data = new_data; + capacity = new_capacity; + } + + public void add_type (uint32 type) { + if (size >= capacity) { + increase_capacity (); + } + + data[size].type = type; + size++; + } + + public void add (double d) { + if (size >= capacity) { + increase_capacity (); + } + + data[size].value = d; + size++; + } + + public Doubles copy () { + Doubles d = new Doubles (); + delete d.data; + d.data = new PointValue[capacity]; + d.capacity = capacity; + d.size = size; + Posix.memcpy (d.data, data, sizeof (PointValue) * size); + return d; + } + + public double get_double (int index) { + if (unlikely (index < 0)) { + warning ("index < 0"); + return 0; + } + + if (unlikely (index >= size)) { + warning ("index >= size"); + return 0; + } + + return data[index].value; + } + + public uint32 get_point_type (int index) { + if (unlikely (index < 0)) { + warning ("index < 0"); + return 0; + } + + if (unlikely (index >= size)) { + warning ("index >= size"); + return 0; + } + + return data[index].type; + } + + public void set_point_type (int index, uint32 type) { + if (unlikely (index < 0)) { + warning ("index < 0"); + return; + } + + if (unlikely (index >= size)) { + warning ("index >= size"); + return; + } + + data[index].type = type; + } + + public string get_string (int i) { + return round (get_double (i)); + } + + public static string round (double p) { + string v = p.to_string (); + char[] c = new char [501]; + + v = p.format (c, "%3.5f"); + + if (v.index_of ("e") != -1) { + return "0.0"; + } + + return v; + } + } + + } +
diff --git libsvgbird/Ellipse.vala(new)
--- /dev/null +++ b/libsvgbird/Ellipse.vala @@ -1,1 +1,67 @@ + /* + Copyright (C) 2016 Johan Mattsson + + 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 3 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. + */ + + using Cairo; + using Math; + + namespace SvgBird { + + public class Ellipse : Object { + + public double cx = 0; + public double cy = 0; + public double rx = 0; + public double ry = 0; + + public Ellipse () { + } + + public override bool is_over (double x, double y) { + to_object_view (ref x, ref y); + double point_x = x - cx; + double point_y = y - cy; + return (point_x * point_x) / (rx * rx) + (point_y * point_y) / (ry * ry) <= 1; + } + + public override void draw_outline (Context cr) { + cr.save (); + cr.translate (cx, cy); + cr.scale (rx, ry); + cr.move_to (1, 0); + cr.arc (0, 0, 1, 0, 2 * PI); + cr.restore (); + } + + public override bool is_empty () { + return false; + } + + public override Object copy () { + Ellipse e = new Ellipse (); + Object.copy_attributes (this, e); + e.cx = cx; + e.cy = cy; + e.rx = rx; + e.ry = ry; + return e; + } + + public override string to_string () { + return "Ellipse"; + } + + } + + }
diff --git libsvgbird/EmptyObject.vala(new)
--- /dev/null +++ b/libsvgbird/EmptyObject.vala @@ -1,1 +1,45 @@ + /* + Copyright (C) 2016 Johan Mattsson + + 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 3 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. + */ + + using Cairo; + + namespace SvgBird { + + public class EmptyObject : Object { + + public EmptyObject () { + } + + public override bool is_over (double x, double y) { + return false; + } + + public override void draw_outline (Context cr) { + } + + public override bool is_empty () { + return false; + } + + public override Object copy () { + return new EmptyObject (); + } + + public override string to_string () { + return "Empty"; + } + } + + }