summaryrefslogtreecommitdiffstats
path: root/src/io
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:29:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:29:01 +0000
commit35a96bde514a8897f6f0fcc41c5833bf63df2e2a (patch)
tree657d15a03cc46bd099fc2c6546a7a4ad43815d9f /src/io
parentInitial commit. (diff)
downloadinkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.tar.xz
inkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.zip
Adding upstream version 1.0.2.upstream/1.0.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/io')
-rw-r--r--src/io/CMakeLists.txt36
-rw-r--r--src/io/README35
-rw-r--r--src/io/crystalegg.xml767
-rw-r--r--src/io/dir-util.cpp263
-rw-r--r--src/io/dir-util.h75
-rw-r--r--src/io/doc2html.xsl64
-rw-r--r--src/io/file-export-cmd.cpp786
-rw-r--r--src/io/file-export-cmd.h80
-rw-r--r--src/io/file.cpp148
-rw-r--r--src/io/file.h40
-rw-r--r--src/io/http.cpp153
-rw-r--r--src/io/http.h41
-rw-r--r--src/io/resource-manager.cpp450
-rw-r--r--src/io/resource-manager.h48
-rw-r--r--src/io/resource.cpp477
-rw-r--r--src/io/resource.h111
-rw-r--r--src/io/stream/Makefile.tst48
-rw-r--r--src/io/stream/README13
-rw-r--r--src/io/stream/bufferstream.cpp144
-rw-r--r--src/io/stream/bufferstream.h98
-rw-r--r--src/io/stream/gzipstream.cpp459
-rw-r--r--src/io/stream/gzipstream.h124
-rw-r--r--src/io/stream/inkscapestream.cpp800
-rw-r--r--src/io/stream/inkscapestream.h668
-rw-r--r--src/io/stream/streamtest.cpp256
-rw-r--r--src/io/stream/stringstream.cpp125
-rw-r--r--src/io/stream/stringstream.h105
-rw-r--r--src/io/stream/uristream.cpp231
-rw-r--r--src/io/stream/uristream.h101
-rw-r--r--src/io/stream/xsltstream.cpp248
-rw-r--r--src/io/stream/xsltstream.h140
-rw-r--r--src/io/sys.cpp361
-rw-r--r--src/io/sys.h69
33 files changed, 7564 insertions, 0 deletions
diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt
new file mode 100644
index 0000000..3a5d8ae
--- /dev/null
+++ b/src/io/CMakeLists.txt
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(io_SRC
+ dir-util.cpp
+ file.cpp
+ file-export-cmd.cpp
+ resource.cpp
+ resource-manager.cpp
+ stream/bufferstream.cpp
+ stream/gzipstream.cpp
+ stream/inkscapestream.cpp
+ stream/stringstream.cpp
+ stream/uristream.cpp
+ stream/xsltstream.cpp
+ sys.cpp
+ http.cpp
+
+ # -------
+ # Headers
+ dir-util.h
+ file.h
+ file-export-cmd.h
+ resource.h
+ resource-manager.h
+ stream/bufferstream.h
+ stream/gzipstream.h
+ stream/inkscapestream.h
+ stream/stringstream.h
+ stream/uristream.h
+ stream/xsltstream.h
+ sys.h
+ http.h
+)
+
+# add_inkscape_lib(io_LIB "${io_SRC}")
+add_inkscape_source("${io_SRC}")
diff --git a/src/io/README b/src/io/README
new file mode 100644
index 0000000..9d971b7
--- /dev/null
+++ b/src/io/README
@@ -0,0 +1,35 @@
+
+This directory contains code related to input and output.
+
+
+Note that input and output to particular file formats may be implemented elsewhere:
+
+1. Internal extensions. See src/extensions/internal
+
+ Input: CDR, EMF, WMF VSD WPG
+ Output: SVG, PNG (via Cairo), PS, EPS, PDF, POV, ODF, EMF, WMF, XAML, LaTex.
+
+2. External extensions (Python). See share/extensions
+
+ Input: PS, EPS, PDF, SK, XAML, DXF, DIA, AI, ?
+ Output: SVG (Layers/Optimized), SK1, XCF, HTML5, SIF, PTL, HPGL, DXF, FXG, XAML(?), CANVAS, ?
+
+3. SVG (XML) low level code: src/xml/repr-io.h
+
+
+To do:
+
+1. Move all file related code here (other than extensions).
+2. Move extension input/output code into subdirectories within src/extensions/internal and share/extensions.
+3. Separate out creating a document and creating a document window. The former belongs here, the later in src/ui.
+4. Use std::string for all file names and use glibmm file utilities.
+5. Use Glib::ustring for URI's and use Inkscape's URI utilities (if not available in glibmm).
+6. Rewrite file export code to share a common base and to allow easy export of objects. Should be Gio::Action based.
+ Things like cropping, selecting an object, hiding other objects, etc. should be done before the document is passed
+ to the file type specific export code. This way, we can use the same export options for all file types (where they
+ make sense). Only type specific options (e.g. PS Level) should be handled by the type specific code.
+
+
+Files to move here:
+ src/util/ziptool.h (used by ODF, note SVG uses Inkscape::IO::GzipInputStream, can that be used instead?).
+ src/helper/png-write.h
diff --git a/src/io/crystalegg.xml b/src/io/crystalegg.xml
new file mode 100644
index 0000000..0e916e7
--- /dev/null
+++ b/src/io/crystalegg.xml
@@ -0,0 +1,767 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<book>
+
+<bookinfo>
+ <title>The Crystal Egg</title>
+ <author><firstname>H. G.</firstname><surname>Wells</surname></author>
+</bookinfo>
+
+
+<chapter>
+
+<para>
+ There was, until a year ago, a little and very grimy-looking shop
+near Seven Dials over which, in weather-worn yellow lettering, the name
+of "C. Cave, Naturalist and Dealer in Antiquities," was inscribed. The
+contents of its window were curiously variegated. They comprised some
+elephant tusks and an imperfect set of chessmen, beads and weapons, a
+box of eyes, two skulls of tigers and one human, several moth-eaten
+stuffed monkeys (one holding a lamp), an old-fashioned cabinet, a
+flyblown ostrich egg or so, some fishing-tackle, and an extraordinarily
+dirty, empty glass fish tank. There was also, at the moment the story
+begins, a mass of crystal, worked into the shape of an egg and
+brilliantly polished. And at that two people, who stood outside the
+window, were looking, one of them a tall, thin clergyman, the other a
+black-bearded young man of dusky complexion and unobtrusive costume. The
+dusky young man spoke with eager gestulation, and seemed anxious for his
+companion to purchase the article.
+</para>
+
+<para>
+ While they were there, Mr. Cave came into his shop, his beard still
+wagging with the bread and butter of his tea. When he saw these men and
+the object of their regard, his countenance fell. He glanced guiltily
+over his shoulder, and softly shut the door. He was a little old man,
+with pale face and peculiar watery blue eyes; his hair was a dirty grey,
+and he wore a shabby blue frock-coat, an ancient silk hat, and carpet
+slippers very much down at heel. He remained watching the two men as
+they talked. The clergyman went deep into his trouser pocket, examined a
+handful of money, and showed his teeth in an agreeable smile. Mr. Cave
+seemed still more depressed when they came into the shop.
+</para>
+
+<para>
+ The clergyman, without any ceremony, asked the price of the crystal
+egg. Mr. Cave glanced nervously towards the door leading into the
+parlour, and said five pounds. The clergyman protested that the price
+was high, to his companion as well as to Mr. Cave -- it was, indeed,
+very much more than Mr. Cave had intended to ask, when he had stocked
+the article -- and an attempt at bargaining ensued. Mr. Cave stepped to
+the shop-door, and held it open. "Five pounds is my price," he said, as
+though he wished to save himself the trouble of unprofitable discussion.
+As he did so, the upper portion of a woman's face appeared above the
+blind in the glass upper panel of the door leading into the parlour, and
+stared curiously at the two customers. "Five pounds is my price," said
+Mr. Cave, with a quiver in his voice.
+</para>
+
+<para>
+ The swarthy young man had so far remained a spectator, watching Cave
+keenly. Now he spoke. "Give him five pounds," he said. The clergyman
+glanced at him to see if he were in earnest, and, when he looked at Mr.
+Cave again, he saw that the latter's face was white. "It's a lot of
+money," said the clergyman, and, diving into his pocket, began counting
+his resources. He had little more than thirty shillings, and he appealed
+to his companion, with whom he seemed to be on terms of considerable
+intimacy. This gave Mr. Cave an opportunity of collecting his thoughts,
+and he began to explain in an agitated manner that the crystal was not,
+as a matter of fact, entirely free for sale. His two customers were
+naturally surprised at this, and inquired why he had not thought of that
+before he began to bargain. Mr. Cave became confused, but he stuck to
+his story, that the crystal was not in the market that afternoon, that a
+probable purchaser of it had already appeared. The two, treating this as
+an attempt to raise the price still further, made as if they would leave
+the shop. But at this point the parlour door opened, and the owner of
+the dark fringe and the little eyes appeared.
+</para>
+
+<para>
+ She was a coarse-featured, corpulent woman, younger and very much
+larger than Mr. Cave; she walked heavily, and her face was flushed.
+"That crystal is for sale, she said. "And five pounds is a good enough
+price for it. I can't think what you're about, Cave, not to take the
+gentleman's offer!"
+</para>
+
+<para>
+ Mr. Cave, greatly perturbed by the irruption, looked angrily at her
+over the rims of his spectacles, and, without excessive assurance,
+asserted his right to manage his business in his own way. An altercation
+began. The two customers watched the scene with interest and some
+amusement, occasionally assisting Mrs. Cave with suggestions. Mr. Cave,
+hard driven, persisted in a confused and impossible story of an enquiry
+for the crystal that morning, and his agitation became painful. But he
+stuck to his point with extraordinary persistence.
+It was the young Oriental who ended this curious controversy. He
+proposed that they should call again in the course of two days -- so as
+to give the alleged enquirer a fair chance. "And then we must insist,"
+said the clergyman. "Five pounds." Mrs. Cave took it on herself to
+apologise for her husband, explaining that he was sometimes "a little
+odd," and as the two customers left, the couple prepared for a free
+discussion of the incident in all its bearings.
+</para>
+
+<para>
+ Mrs. Cave talked to her husband with singular directness. The poor
+little man, quivering with emotion, muddled himself between his stories,
+maintaining on the one hand that he had another customer in view, and on
+the other asserting that the crystal was honestly worth ten guineas.
+"Why did you ask five pounds?" said his wife. "Do let me manage my
+business my own way!" said Mr. Cave.
+</para>
+
+<para>
+ Mr. Cave had living with him a step-daughter and a step-son, and at
+supper that night the transaction was re-discussed. None of them had a
+high opinion of Mr. Cave's business methods, and this action seemed a
+culminating folly.
+</para>
+
+<para>
+ "It's my opinion he's refused that crystal before," said the
+step-son, a loose-limbed lout of eighteen.
+</para>
+
+<para>
+ "But Five Pounds!" said the step-daughter, an argumentative young
+woman of six-and-twenty.
+</para>
+
+<para>
+ Mr. Cave's answers were wretched; he could only mumble weak
+assertions that he knew his own business best. They drove him from his
+half-eaten supper into the shop, to close it for the night, his ears
+aflame and tears of vexation behind his spectacles. "Why had he left the
+crystal in the window so long? The folly of it!" That was the trouble
+closest in his mind. For a time he could see no way of evading sale.
+</para>
+
+<para>
+ After supper his step-daughter and step-son smartened themselves up
+and went out and his wife retired upstairs to reflect upon the business
+aspects of the crystal, over a little sugar and lemon and so forth in
+hot water. Mr. Cave went into the shop, and stayed there until late,
+ostensibly to make ornamental rockeries for gold-fish cases but really
+for a private purpose that will be better explained later. The next day
+Mrs. Cave found that the crystal had been removed from the window, and
+was lying behind some second-hand books on angling. She replaced it in a
+conspicuous position. But she did not argue further about it, as a
+nervous headache disinclined her from debate. Mr. Cave was always
+disinclined. The day passed disagreeably. Mr. Cave was, if anything,
+more absent-minded than usual, and uncommonly irritable withal. In the
+afternoon, when his wife was taking her customary sleep, he removed the
+crystal from the window again.
+</para>
+
+<para>
+ The next day Mr. Cave had to deliver a consignment of dog-fish at one
+of the hospital schools, where they were needed for dissection. In his
+absence Mrs. Cave's mind reverted to the topic of the crystal, and the
+methods of expenditure suitable to a windfall of five pounds. She had
+already devised some very agreeable expedients, among others a dress of
+green silk for herself and a trip to Richmond, when a jangling of the
+front door bell summoned her into the shop. The customer was an
+examination coach who came to complain of the non-delivery of certain
+frogs asked for the previous day. Mrs. Cave did not approve of this
+particular branch of Mr. Cave's business, and the gentleman, who had
+called in a somewhat aggressive mood, retired after a brief exchange of
+words -- entirely civil so far as he was concerned. Mrs. Cave's eye then
+naturally turned to the window; for the sight of the crystal was an
+assurance of the five pounds and of her dreams. What was her surprise to
+find it gone!
+</para>
+
+<para>
+ She went to the place behind the locker on the counter, where she had
+discovered it the day before. It was not there; and she immediately
+began an eager search about the shop.
+</para>
+
+<para>
+ When Mr. Cave returned from his business with the dog-fish, about a
+quarter to two in the afternoon, he found the shop in some confusion,
+and his wife, extremely exasperated and on her knees behind the counter,
+routing among his taxidermic material. Her face came up hot and angry
+over the counter, as the jangling bell announced his return, and she
+forthwith accused him of "hiding it."
+</para>
+
+<para>
+ "Hid what?" asked Mr. Cave.
+</para>
+
+<para>
+ "The crystal!"
+</para>
+
+<para>
+ At that Mr. Cave, apparently much surprised, rushed to the window.
+"Isn't it here?" he said. "Great Heavens! what has become of it?"
+</para>
+
+<para>
+ Just then, Mr. Cave's step-son re-entered the shop from the inner
+room -- he had come home a minute or so before Mr. Cave -- and he was
+blaspheming freely. He was apprenticed to a second-hand furniture dealer
+down the road, but he had his meals at home, and he was naturally
+annoyed to find no dinner ready.
+</para>
+
+<para>
+ But, when he heard of the loss of the crystal, he forgot his meal,
+and his anger was diverted from his mother to his step-father. Their
+first idea, of course, was that he had hidden it. But Mr. Cave stoutly
+denied all knowledge of its fate -- freely offering his bedabbled
+affidavit in the matter -- and at last was worked up to the point of
+accusing, first, his wife and then his step-son of having taken it with
+a view to a private sale. So began an exceedingly acrimonious and
+emotional discussion, which ended for Mrs. Cave in a peculiar nervous
+condition midway between hysterics and amuck, and caused the step-son to
+be half-an-hour late at the furniture establishment in the afternoon.
+Mr. Cave took refuge from his wife's emotions in the shop.
+</para>
+
+<para>
+ In the evening the matter was resumed, with less passion and in a
+judicial spirit, under the presidency of the step-daughter. The supper
+passed unhappily and culminated in a painful scene. Mr. Cave gave way at
+last to extreme exasperation, and went out banging the front door
+violently. The rest of the family, having discussed him with the freedom
+his absence warranted, hunted the house from garret to cellar, hoping to
+light upon the crystal.
+</para>
+
+<para>
+ The next day the two customers called again. They were received by
+Mrs. Cave almost in tears. It transpired that no one could imagine all
+that she had stood from Cave at various times in her married pilgrimage.
+. . . She also gave a garbled account of the disappearance. The
+clergyman and the Oriental laughed silently at one another, and said it
+was very extraordinary. As Mrs. Cave seemed disposed to give them the
+complete history of her life they made to leave the shop. Thereupon Mrs.
+Cave, still clinging to hope, asked for the clergyman's address, so
+that, if she could get anything out of Cave, she might communicate it.
+The address was duly given, but apparently was afterwards mislaid. Mrs.
+Cave can remember nothing about it.
+</para>
+
+<para>
+ In the evening of that day, the Caves seem to have exhausted their
+emotions, and Mr. Cave, who had been out in the afternoon, supped in a
+gloomy isolation that contrasted pleasantly with the impassioned
+controversy of the previous days. For some time matters were very badly
+strained in the Cave household, but neither crystal nor customer
+reappeared.
+</para>
+
+<para>
+ Now, without mincing the matter, we must admit that Mr. Cave was a
+liar. He knew perfectly well where the crystal was. It was in the rooms
+of Mr. Jacoby Wace, Assistant Demonstrator at St. Catherine's Hospital,
+Westbourne Street. It stood on the sideboard partially covered by a
+black velvet cloth, and beside a decanter of American whisky. It is from
+Mr. Wace, indeed, that the particulars upon which this narrative is
+based were derived. Cave had taken off the thing to the hospital hidden
+in the dog-fish sack, and there had pressed the young investigator to
+keep it for him. Mr. Wace was a little dubious at first. His
+relationship to Cave was peculiar. He had a taste for singular
+characters, and he had more than once invited the old man to smoke and
+drink in his rooms, and to unfold his rather amusing views of life in
+general and of his wife in particular. Mr. Wace had encountered Mrs.
+Cave, too, on occasions when Mr. Cave was not at home to attend to him.
+He knew the constant interference to which Cave was subjected, and
+having weighed the story judicially, he decided to give the crystal a
+refuge. Mr. Cave promised to explain the reasons for his remarkable
+affection for the crystal more fully
+on a later occasion, but he spoke distinctly of seeing visions therein.
+He called on Mr. Wace the same evening.
+</para>
+
+<para>
+ He told a complicated story. The crystal he said had come into his
+possession with other oddments at the forced sale of another curiosity
+dealer's effects, and not knowing what its value might be, he had
+ticketed it at ten shillings. It had hung upon his hands at that price
+for some months, and he was thinking of "reducing the figure," when he
+made a singular discovery.
+</para>
+
+<para>
+ At that time his health was very bad -- and it must be borne in mind
+that, throughout all this experience, his physical condition was one of
+ebb -- and he was in considerable distress by reason of the negligence,
+the positive ill-treatment even, he received from his wife and
+step-children. His wife was vain, extravagant, unfeeling and had a
+growing taste for private drinking; his step-daughter was mean and
+over-reaching; and his step-son had conceived a violent dislike for him,
+and lost no chance of showing it. The requirements of his business
+pressed heavily upon him, and Mr. Wace does not think that he was
+altogether free from occasional intemperance. He had begun life in a
+comfortable position, he was a man of fair education, and he suffered,
+for weeks at a stretch, from melancholia and insomnia. Afraid to disturb
+his family, he would slip quietly from his wife's side, when his
+thoughts became intolerable, and wander about the house. And about three
+o'clock one morning, late in August, chance directed him into the shop.
+</para>
+
+<para>
+ The dirty little place was impenetrably black except in one spot,
+where he perceived an unusual glow of light. Approaching this, he
+discovered it to be the crystal egg, which was standing on the corner of
+the counter towards the window. A thin ray smote through a crack in the
+shutters, impinged upon the object, and seemed as it were to fill its
+entire interior.
+</para>
+
+<para>
+ It occurred to Mr. Cave that this was not in accordance with the laws
+of optics as he had known them in his younger days. He could understand
+the rays being refracted by the crystal and coming to a focus in its
+interior, but this diffusion jarred with his physical conceptions. He
+approached the crystal nearly, peering into it and round it, with a
+transient revival of the scientific curiosity that in his youth had
+determined his choice of a calling. He was surprised to find the light
+not steady, but writhing within the substance of the egg, as though that
+object was a hollow sphere of some luminous vapour. In moving about to
+get different points of view, he suddenly found that he had come between
+it and the ray, and that the crystal none the less remained luminous.
+Greatly astonished, he lifted it out of the light ray and carried it to
+the darkest part of the shop. It remained
+bright for some four or five minutes, when it slowly faded and went out.
+He placed it in the thin streak of daylight, and its luminousness was
+almost immediately restored.
+</para>
+
+<para>
+ So far, at least, Mr. Wace was able to verify the remarkable story of
+Mr. Cave. He has himself repeatedly held this crystal in a ray of light
+(which had to be of a less diameter than one millimetre). And in a
+perfect darkness, such as could be produced by velvet wrapping, the
+crystal did undoubtedly appear very faintly phosphorescent. It would
+seem, however, that the luminousness was of some exceptional sort, and
+not equally visible to all eyes; for Mr. Harbinger -- whose name will be
+familiar to the scientific reader in connection with the Pasteur
+Institute -- was quite unable to see any light whatever. And Mr. Wace's
+own capacity for its appreciation was out of comparison inferior to that
+of Mr. Cave's. Even with Mr. Cave the power varied very considerably:
+his vision was most vivid during states of extreme weakness and fatigue.
+</para>
+
+<para>
+ Now, from the outset this light in the crystal exercised a curious
+fascination upon Mr. Cave. And it says more for his loneliness of soul
+than a volume of pathetic writing could do, that he told no human being
+of his curious observations. He seems to have been living in such an
+atmosphere of petty spite that to admit the existence of a pleasure
+would have been to risk the loss of it. He found that as the dawn
+advanced, and the amount of diffused light increased, the crystal became
+to all appearance non-luminous. And for some time he was unable to see
+anything in it, except at night-time, in dark corners of the shop.
+</para>
+
+<para>
+ But the use of an old velvet cloth, which he used as a background for
+a collection of minerals, occurred to him, and by doubling this, and
+putting it over his head and hands, he was able to get a sight of the
+luminous movement within the crystal even in the day-time. He was very
+cautious lest he should be thus discovered by his wife, and he practised
+this occupation only in the afternoons, while she was asleep upstairs,
+and then circumspectly in a hollow under the counter. And one day,
+turning the crystal about in his hands, he saw something. It came and
+went like a flash, but it gave him the impression that the object had
+for a moment opened to him the view of a wide and spacious and strange
+country; and, turning it about, he did, just as the light faded, see the
+same vision again.
+</para>
+
+<para>
+ Now, it would be tedious and unnecessary to state all the phases of
+Mr. Cave's discovery from this point. Suffice that the effect was this:
+the crystal, being peered into at an angle of about 137 degrees from the
+direction of the illuminating ray, gave a clear and consistent picture
+of a wide and peculiar country-side. It was not dream-like at
+all: it produced a definite impression of reality, and the better the
+light the more real and solid it seemed. It was a moving picture: that
+is to say, certain objects moved in it, but slowly in an orderly manner
+like real things, and, according as the direction of the lighting and
+vision changed, the picture changed also. It must, indeed, have been
+like looking through an oval glass at a view, and turning the glass
+about to get at different aspects.
+</para>
+
+<para>
+ Mr. Cave's statements, Mr. Wace assures me, were extremely
+circumstantial, and entirely free from any of that emotional quality
+that taints hallucinatory impressions. But it must be remembered that
+all the efforts of Mr. Wace to see any similar clarity in the faint
+opalescence of the crystal were wholly unsuccessful, try as he would.
+The difference in intensity of the impressions received by the two men
+was very great, and it is quite conceivable that what was a view to Mr.
+Cave was a mere blurred nebulosity to Mr. Wace.
+</para>
+
+<para>
+ The view, as Mr. Cave described it, was invariably of an extensive
+plain, and he seemed always to be looking at it from a considerable
+height, as if from a tower or a mast. To the east and to the west the
+plain was bounded at a remote distance by vast reddish cliffs, which
+reminded him of those he had seen in some picture; but what the picture
+was Mr. Wace was unable to ascertain. These cliffs passed north and
+south -- he could tell the points of the compass by the stars that were
+visible of a night -- receding in an almost illimitable perspective and
+fading into the mists of the distance before they met. He was nearer the
+eastern set of cliffs, on the occasion of his first vision the sun was
+rising over them, and black against the sunlight and pale against their
+shadow appeared a multitude of soaring forms that Mr. Cave regarded as
+birds. A vast range of buildings spread below him; he seemed to be
+looking down upon them; and, as they approached the blurred and
+refracted edge of the picture, they became indistinct. There were also
+trees curious in shape, and in colouring, a deep mossy green and an
+exquisite grey, beside a wide and shining canal. And something great and
+brilliantly coloured flew across the picture. But the first time Mr.
+Cave saw these pictures he saw only in flashes, his hands shook, his
+head moved, the vision came and went, and grew foggy and indistinct. And
+at first he had the greatest difficulty in finding the picture again
+once the direction of it was lost.
+</para>
+
+<para>
+ His next clear vision, which came about a week after the first, the
+interval having yielded nothing but tantalising glimpses and some useful
+experience, showed him the view down the length of the valley. The view
+was different, but he had a curious persuasion, which his subsequent
+observations abundantly confirmed, that he was regarding this strange
+world from exactly the same spot, although he was looking
+in a different direction. The long facade of the great building, whose
+roof he had looked down upon before, was now receding in perspective. He
+recognised the roof. In the front of the facade was a terrace of massive
+proportions and extraordinary length, and down the middle of the
+terrace, at certain intervals, stood huge but very graceful masts,
+bearing small shiny objects which reflected the setting sun. The import
+of these small objects did not occur to Mr. Cave until some time after,
+as he was describing the scene to Mr. Wace. The terrace overhung a
+thicket of the most luxuriant and graceful vegetation, and beyond this
+was a wide grassy lawn on which certain broad creatures, in form like
+beetles but enormously larger, reposed. Beyond this again was a richly
+decorated causeway of pinkish stone; and beyond that, and lined with
+dense red weeds, and passing up the valley exactly parallel with the
+distant cliffs, was a broad and mirror-like expanse of water. The air
+seemed full of squadrons of great birds, manoeuvring in stately curves;
+and across the river was a multitude of splendid buildings, richly
+coloured and glittering with metallic tracery and facets, among a forest
+of moss-like and lichenous trees. And suddenly something flapped
+repeatedly across the vision, like the fluttering of a jewelled fan or
+the beating of a wing, and a face, or rather the upper part of a face
+with very large eyes, came as it were close to his own and as if on the
+other side of the crystal. Mr. Cave was so startled and so impressed by
+the absolute reality of these eyes, that he drew his head back from the
+crystal to look behind it. He had become so absorbed in watching that he
+was quite surprised to find himself in the cool darkness of his little
+shop, with its familiar odour of methyl, mustiness, and decay. And, as
+he blinked about him, the glowing crystal faded, and went out.
+</para>
+
+<para>
+ Such were the first general impressions of Mr. Cave. The story is
+curiously direct and circumstantial. From the outset, when the valley
+first flashed momentarily on his senses, his imagination was strangely
+affected, and, as he began to appreciate the details of the scene he
+saw, his wonder rose to the point of a passion. He went about his
+business listless and distraught, thinking only of the time when he
+should be able to return to his watching. And then a few weeks after his
+first sight of the valley came the two customers, the stress and
+excitement of their offer, and the narrow escape of the crystal from
+sale, as I have already told.
+</para>
+
+<para>
+ Now, while the thing was Mr. Cave's secret, it remained a mere
+wonder, a thing to creep to covertly and peep at, as a child might peep
+upon a forbidden garden. But Mr. Wace has, for a young scientific
+investigator, a particularly lucid and consecutive habit of mind.
+Directly the crystal and its story came to him, and he had satisfied
+himself, by seeing the phosphorescence with his own eyes, that there
+really was a certain evidence for Mr. Cave's statements, he proceeded to
+develop the matter systematically. Mr. Cave was only too eager to come
+and feast his eyes on this wonderland he saw, and he came every night
+from half-past eight until half-past ten, and sometimes, in Mr. Wace's
+absence, during the day. On Sunday afternoons, also, he came. From the
+outset Mr. Wace made copious notes, and it was due to his scientific
+method that the relation between the direction from which the initiating
+ray entered the crystal and the orientation of the picture were proved.
+And, by covering the crystal in a box perforated only with a small
+aperture to admit the exciting ray, and by substituting black holland
+for his buff blinds, he greatly improved the conditions of the
+observations; so that in a little while they were able to survey the
+valley in any direction they desired.
+</para>
+
+<para>
+ So having cleared the way, we may give a brief account of this
+visionary world within the crystal. The things were in all cases seen by
+Mr. Cave, and the method of working was invariably for him to watch the
+crystal and report what he saw, while Mr. Wace (who as a science student
+had learnt the trick of writing in the dark) wrote a brief note of his
+report. When the crystal faded, it was put into its box in the proper
+position and the electric light turned on. Mr. Wace asked questions, and
+suggested observations to clear up difficult points. Nothing, indeed,
+could have been less visionary and more matter-of-fact.
+</para>
+
+<para>
+ The attention of Mr. Cave had been speedily directed to the bird-like
+creatures he had seen so abundantly present in each of his earlier
+visions. His first impression was soon corrected, and he considered for
+a time that they might represent a diurnal species of bat. Then he
+thought, grotesquely enough, that they might be cherubs. Their heads
+were round, and curiously human, and it was the eyes of one of them that
+had so startled him on his second observation. They had broad, silvery
+wings, not feathered, but glistening almost as brilliantly as new-killed
+fish and with the same subtle play of colour, and these wings were not
+built on the plan of a bird-wing or bat, Mr. Wace learned, but supported
+by curved ribs radiating from the body. (A sort of butterfly wing with
+curved ribs seems best to express their appearance.) The body was small,
+but fitted with two bunches of prehensile organs, like long tentacles,
+immediately under the mouth. Incredible as it appeared to Mr. Wace, the
+persuasion at last became irresistible, that it was these creatures
+which owned the great quasi-human buildings and the magnificent garden
+that made the broad valley so splendid. And Mr. Cave perceived that the
+buildings, with other peculiarities, had no doors, but that the great
+circular windows, which
+opened freely, gave the creatures egress and entrance. They would alight
+upon their tentacles, fold their wings to a smallness almost rod-like,
+and hop into the interior. But among them was a multitude of
+smaller-winged creatures, like great dragon-flies and moths and flying
+beetles, and across the greensward brilliantly-coloured gigantic
+ground-beetles crawled lazily to and fro. Moreover, on the causeways and
+terraces, large-headed creatures similar to the greater winged flies,
+but wingless, were visible, hopping busily upon their hand-like tangle
+of tentacles.
+</para>
+
+<para>
+ Allusion has already been made to the glittering objects upon masts
+that stood upon the terrace of the nearer building. It dawned upon Mr.
+Cave, after regarding one of these masts very fixedly on one
+particularly vivid day, that the glittering object there was a crystal
+exactly like that into which he peered. And a still more careful
+scrutiny convinced him that each one in a vista of nearly twenty carried
+a similar object.
+</para>
+
+<para>
+ Occasionally one of the large flying creatures would flutter up to
+one, and, folding its wings and coiling a number of its tentacles about
+the mast, would regard the crystal fixedly for a space, -- sometimes for
+as long as fifteen minutes. And a series of observations, made at the
+suggestion of Mr. Wace, convinced both watchers that, so far as this
+visionary world was concerned, the crystal into which they peered
+actually stood at the summit of the end-most mast on the terrace, and
+that on one occasion at least one of these inhabitants of this other
+world had looked into Mr. Cave's face while he was making these
+observations.
+</para>
+
+<para>
+ So much for the essential facts of this very singular story. Unless
+we dismiss it all as the ingenious fabrication of Mr. Wace, we have to
+believe one of two things: either that Mr. Cave's crystal was in two
+worlds at once, and that, while it was carried about in one, it remained
+stationary in the other, which seems altogether absurd; or else that it
+had some peculiar relation of sympathy with another and exactly similar
+crystal in this other world, so that what was seen in the interior of
+the one in this world, was, under suitable conditions, visible to an
+observer in the corresponding crystal in the other world; and vice
+versa. At present, indeed, we do not know of any way in which two
+crystals could so come en rapport, but nowadays we know enough to
+understand that the thing is not altogether impossible. This view of the
+crystals as en rapport was the supposition that occurred to Mr. Wace,
+and to me at least it seems extremely plausible. . . .
+</para>
+
+<para>
+ And where was this other world? On this, also, the alert intelligence
+of Mr. Wace speedily threw light. After sunset, the sky darkened
+rapidly -- there was a very brief twilight interval indeed -- and the
+stars shone out. They were recognisably the same as those we see,
+arranged in the same constellations. Mr. Cave recognised the Bear, the
+Pleiades, Aldebaran, and Sirius: so that the other world must be
+somewhere in the solar system, and, at the utmost, only a few hundreds
+of millions of miles from our own. Following up this clue, Mr. Wace
+learned that the midnight sky was a darker blue even than our midwinter
+sky, and that the sun seemed a little smaller. And there were two small
+moons! "like our moon but smaller, and quite differently marked" one of
+which moved so rapidly that its motion was clearly visible as one
+regarded it. These moons were never high in the sky, but vanished as
+they rose: that is, every time they revolved they were eclipsed because
+they were so near their primary planet. And all this answers quite
+completely, although. Mr. Cave did not know it, to what must be the
+condition of things on Mars.
+</para>
+
+<para>
+ Indeed, it seems an exceedingly plausible conclusion that peering
+into this crystal Mr. Cave did actually see the planet Mars and its
+inhabitants. And, if that be the case, then the evening star that shone
+so brilliantly in the sky of that distant vision, was neither more nor
+less than our own familiar earth.
+</para>
+
+<para>
+ For a time the Martians -- if they were Martians -- do not seem to
+have known of Mr. Cave's inspection. Once or twice one would come to
+peer, and go away very shortly to some other mast, as though the vision
+was unsatisfactory. During this time Mr. Cave was able to watch the
+proceedings of these winged people without being disturbed by their
+attentions, and, although his report is necessarily vague and
+fragmentary, it is nevertheless very suggestive. Imagine the impression
+of humanity a Martian observer would get who, after a difficult process
+of preparation and with considerable fatigue to the eyes, was able to
+peer at London from the steeple of St. Martin's Church for stretches, at
+longest, of four minutes at a time. Mr. Cave was unable to ascertain if
+the winged Martians were the same as the Martians who hopped about the
+causeways and terraces, and if the latter could put on wings at will. He
+several times saw certain clumsy bipeds, dimly suggestive of apes, white
+and partially translucent, feeding among certain of the lichenous trees,
+and once some of these fled before one of the hopping, round-headed
+Martians. The latter caught one in its tentacles, and then the picture
+faded suddenly and left Mr. Cave most tantalisingly in the dark. On
+another occasion a vast thing, that Mr. Cave thought at first was some
+gigantic insect, appeared advancing along the causeway beside the canal
+with extraordinary rapidity. As this drew nearer Mr. Cave perceived that
+it was a mechanism of shining
+metals and of extraordinary complexity. And then, when he looked again,
+it had passed out of sight.
+</para>
+
+<para>
+ After a time Mr. Wace aspired to attract the attention of the
+Martians, and the next time that the strange eyes of one of them
+appeared close to the crystal Mr. Cave cried out and sprang away, and
+they immediately turned on the light and began to gesticulate in a
+manner suggestive of signalling. But when at last Mr. Cave examined the
+crystal again the Martian had departed.
+</para>
+
+<para>
+ Thus far these observations had progressed in early November, and
+then Mr. Cave, feeling that the suspicions of his family about the
+crystal were allayed, began to take it to and fro with him in order
+that, as occasion arose in the daytime or night, he might comfort
+himself with what was fast becoming the most real thing in his existence.
+</para>
+
+<para>
+ In December Mr. Wace's work in connection with a forthcoming
+examination became heavy, the sittings were reluctantly suspended for a
+week, and for ten or eleven days -- he is not quite sure which -- he saw
+nothing of Cave. He then grew anxious to resume these investigations,
+and, the stress of his seasonal labours being abated, he went down to
+Seven Dials. At the corner he noticed a shutter before a bird fancier's
+window, and then another at a cobbler's. Mr. Cave's shop was closed.
+</para>
+
+<para>
+ He rapped and the door was opened by the step-son in black. He at
+once called Mrs. Cave, who was, Mr. Wace could not but observe, in cheap
+but ample widow's weeds of the most imposing pattern. Without any very
+great surprise Mr. Wace learnt that Cave was dead and already buried.
+She was in tears, and her voice was a little thick. She had just
+returned from Highgate. Her mind seemed occupied with her own prospects
+and the honourable details of the obsequies, but Mr. Wace was at last
+able to learn the particulars of Cave's death. He had been found dead in
+his shop in the early morning, the day after his last visit to Mr. Wace,
+and the crystal had been clasped in his stone-cold hands. His face was
+smiling, said Mrs. Cave, and the velvet cloth from the minerals lay on
+the floor at his feet. He must have been dead five or six hours when he
+was found.
+</para>
+
+<para>
+ This came as a great shock to Wace, and he began to reproach himself
+bitterly for having neglected the plain symptoms of the old man's
+ill-health. But his chief thought was of the crystal. He approached that
+topic in a gingerly manner, because he knew Mrs. Cave's peculiarities.
+He was dumbfoundered to learn that it was sold.
+</para>
+
+<para>
+ Mrs. Cave's first impulse, directly Cave's body had been taken
+upstairs, had been to write to the mad clergyman who had offered five
+pounds for the crystal, informing him of its recovery; but after a
+violent hunt in which her daughter joined her, they were convinced
+of the loss of his address. As they were without the means required to
+mourn and bury Cave in the elaborate style the dignity of an old Seven
+Dials inhabitant demands, they had appealed to a friendly
+fellow-tradesman in Great Portland Street. He had very kindly taken over
+a portion of the stock at a valuation. The valuation was his own and the
+crystal egg was included in one of the lots. Mr. Wace, after a few
+suitable consolatory observations, a little offhandedly proffered
+perhaps, hurried at once to Great Portland Street. But there he learned
+that the crystal egg had already been sold to a tall, dark man in grey.
+And there the material facts in this curious, and to me at least very
+suggestive, story come abruptly to an end. The Great Portland Street
+dealer did not know who the tall dark man in grey was, nor had he
+observed him with sufficient attention to describe him minutely. He did
+not even know which way this person had gone after leaving the shop. For
+a time Mr. Wace remained in the shop, trying the dealer's patience with
+hopeless questions, venting his own exasperation. And at last, realising
+abruptly that the whole thing had passed out of his hands, had vanished
+like a vision of the night, he returned to his own rooms, a little
+astonished to find the notes he had made still tangible and visible upon
+his untidy table.
+</para>
+
+<para>
+ His annoyance and disappointment were naturally very great. He made a
+second call (equally ineffectual) upon the Great Portland Street dealer,
+and he resorted to advertisements in such periodicals as were likely to
+come into the hands of a bric-a-brac collector. He also wrote letters to
+The Daily Chronicle and Nature, but both those periodicals, suspecting a
+hoax, asked him to reconsider his action before they printed, and he was
+advised that such a strange story, unfortunately so bare of supporting
+evidence, might imperil his reputation as an investigator. Moreover, the
+calls of his proper work were urgent. So that after a month or so, save
+for an occasional reminder to certain dealers, he had reluctantly to
+abandon the quest for the crystal egg, and from that day to this it
+remains undiscovered. Occasionally, however, he tells me, and I can
+quite believe him, he has bursts of zeal, in which he abandons his more
+urgent occupation and resumes the search.
+</para>
+
+<para>
+ Whether or not it will remain lost for ever, with the material and
+origin of it, are things equally speculative at the present time. If the
+present purchaser is a collector, one would have expected the enquiries
+of Mr. Wace to have readied him through the dealers. He has been able to
+discover Mr. Cave's clergyman and "Oriental" -- no other than the Rev.
+James Parker and the young Prince of Bosso-Kuni in Java. I am obliged to
+them for certain particulars. The object of the Prince was simply
+curiosity -- and extravagance. He was so eager to buy,
+because Cave was so oddly reluctant to sell. It is just as possible that
+the buyer in the second instance was simply a casual purchaser and not a
+collector at all, and the crystal egg, for all I know, may at the
+present moment be within a mile of me, decorating a drawing-room or
+serving as a paper-weight -- its remarkable functions all unknown.
+Indeed, it is partly with the idea of such a possibility that I have
+thrown this narrative into a form that will give it a chance of being
+read by the ordinary consumer of fiction.
+</para>
+
+<para>
+ My own ideas in the matter are practically identical with those of
+Mr. Wace. I believe the crystal on the mast in Mars and the crystal egg
+of Mr. Cave's to be in some physical, but at present quite inexplicable,
+way en rapport, and we both believe further that the terrestrial crystal
+must have been -- possibly at some remote date -- sent hither from that
+planet, in order to give the Martians a near view of our affairs.
+Possibly the fellows to the crystals in the other masts are also on our
+globe. No theory of hallucination suffices for the facts.
+</para>
+
+</chapter>
+
+</book>
+
diff --git a/src/io/dir-util.cpp b/src/io/dir-util.cpp
new file mode 100644
index 0000000..69a01f1
--- /dev/null
+++ b/src/io/dir-util.cpp
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/**
+ * @file
+ * Utility functions for filenames.
+ */
+
+#include <cerrno>
+#include <string>
+#include <cstring>
+#include <glib.h>
+#include "dir-util.h"
+
+std::string sp_relative_path_from_path( std::string const &path, std::string const &base)
+{
+ std::string result;
+ if ( !base.empty() && !path.empty() ) {
+ size_t base_len = base.length();
+ while (base_len != 0
+ && (base[base_len - 1] == G_DIR_SEPARATOR))
+ {
+ --base_len;
+ }
+
+ if ( (path.substr(0, base_len) == base.substr(0, base_len))
+ && (path[base_len] == G_DIR_SEPARATOR))
+ {
+ size_t retPos = base_len + 1;
+ while ( (retPos < path.length()) && (path[retPos] == G_DIR_SEPARATOR) ) {
+ retPos++;
+ }
+ if ( (retPos + 1) < path.length() ) {
+ result = path.substr(retPos);
+ }
+ }
+
+ }
+ if ( result.empty() ) {
+ result = path;
+ }
+ return result;
+}
+
+char const *sp_extension_from_path(char const *const path)
+{
+ if (path == nullptr) {
+ return nullptr;
+ }
+
+ char const *p = path;
+ while (*p != '\0') p++;
+
+ while ((p >= path) && (*p != G_DIR_SEPARATOR) && (*p != '.')) p--;
+ if (* p != '.') return nullptr;
+ p++;
+
+ return p;
+}
+
+
+/* current == "./", parent == "../" */
+static char const dots[] = {'.', '.', G_DIR_SEPARATOR, '\0'};
+static char const *const parent = dots;
+static char const *const current = dots + 1;
+
+char *inkscape_rel2abs(const char *path, const char *base, char *result, const size_t size)
+{
+ const char *pp, *bp;
+ /* endp points the last position which is safe in the result buffer. */
+ const char *endp = result + size - 1;
+ char *rp;
+ int length;
+ if (*path == G_DIR_SEPARATOR)
+ {
+ if (strlen (path) >= size)
+ goto erange;
+ strcpy (result, path);
+ goto finish;
+ }
+ else if (*base != G_DIR_SEPARATOR || !size)
+ {
+ errno = EINVAL;
+ return (nullptr);
+ }
+ else if (size == 1)
+ goto erange;
+ if (!strcmp (path, ".") || !strcmp (path, current))
+ {
+ if (strlen (base) >= size)
+ goto erange;
+ strcpy (result, base);
+ /* rp points the last char. */
+ rp = result + strlen (base) - 1;
+ if (*rp == G_DIR_SEPARATOR)
+ *rp = 0;
+ else
+ rp++;
+ /* rp point NULL char */
+ if (*++path == G_DIR_SEPARATOR)
+ {
+ /* Append G_DIR_SEPARATOR to the tail of path name. */
+ *rp++ = G_DIR_SEPARATOR;
+ if (rp > endp)
+ goto erange;
+ *rp = 0;
+ }
+ goto finish;
+ }
+ bp = base + strlen (base);
+ if (*(bp - 1) == G_DIR_SEPARATOR)
+ --bp;
+ /* up to root. */
+ for (pp = path; *pp && *pp == '.';)
+ {
+ if (!strncmp (pp, parent, 3))
+ {
+ pp += 3;
+ while (bp > base && *--bp != G_DIR_SEPARATOR)
+ ;
+ }
+ else if (!strncmp (pp, current, 2))
+ {
+ pp += 2;
+ }
+ else if (!strncmp (pp, "..\0", 3))
+ {
+ pp += 2;
+ while (bp > base && *--bp != G_DIR_SEPARATOR)
+ ;
+ }
+ else
+ break;
+ }
+ /* down to leaf. */
+ length = bp - base;
+ if (length >= static_cast<int>(size))
+ goto erange;
+ strncpy (result, base, length);
+ rp = result + length;
+ if (*pp || *(pp - 1) == G_DIR_SEPARATOR || length == 0)
+ *rp++ = G_DIR_SEPARATOR;
+ if (rp + strlen (pp) > endp)
+ goto erange;
+ strcpy (rp, pp);
+finish:
+ return result;
+erange:
+ errno = ERANGE;
+ return (nullptr);
+}
+
+char *inkscape_abs2rel(const char *path, const char *base, char *result, const size_t size)
+{
+ const char *pp, *bp, *branch;
+ // endp points the last position which is safe in the result buffer.
+ const char *endp = result + size - 1;
+ char *rp;
+
+ if (*path != G_DIR_SEPARATOR)
+ {
+ if (strlen (path) >= size)
+ goto erange;
+ strcpy (result, path);
+ goto finish;
+ }
+ else if (*base != G_DIR_SEPARATOR || !size)
+ {
+ errno = EINVAL;
+ return (nullptr);
+ }
+ else if (size == 1)
+ goto erange;
+ /* seek to branched point. */
+ branch = path;
+ for (pp = path, bp = base; *pp && *bp && *pp == *bp; pp++, bp++)
+ if (*pp == G_DIR_SEPARATOR)
+ branch = pp;
+ if (((*pp == 0) || ((*pp == G_DIR_SEPARATOR) && (*(pp + 1) == 0))) &&
+ ((*bp == 0) || ((*bp == G_DIR_SEPARATOR) && (*(bp + 1) == 0))))
+ {
+ rp = result;
+ *rp++ = '.';
+ if (*pp == G_DIR_SEPARATOR || *(pp - 1) == G_DIR_SEPARATOR)
+ *rp++ = G_DIR_SEPARATOR;
+ if (rp > endp)
+ goto erange;
+ *rp = 0;
+ goto finish;
+ }
+ if (((*pp == 0) && (*bp == G_DIR_SEPARATOR)) || ((*pp == G_DIR_SEPARATOR) && (*bp == 0)))
+ branch = pp;
+ /* up to root. */
+ rp = result;
+ for (bp = base + (branch - path); *bp; bp++)
+ if (*bp == G_DIR_SEPARATOR && *(bp + 1) != 0)
+ {
+ if (rp + 3 > endp)
+ goto erange;
+ *rp++ = '.';
+ *rp++ = '.';
+ *rp++ = G_DIR_SEPARATOR;
+ }
+ if (rp > endp)
+ goto erange;
+ *rp = 0;
+ /* down to leaf. */
+ if (*branch)
+ {
+ if (rp + strlen (branch + 1) > endp)
+ goto erange;
+ strcpy (rp, branch + 1);
+ }
+ else
+ *--rp = 0;
+finish:
+ return result;
+erange:
+ errno = ERANGE;
+ return (nullptr);
+}
+
+char *prepend_current_dir_if_relative(gchar const *uri)
+{
+ if (!uri) {
+ return nullptr;
+ }
+
+ gchar *full_path = (gchar *) g_malloc (1001);
+ gchar *cwd = g_get_current_dir();
+
+ gsize bytesRead = 0;
+ gsize bytesWritten = 0;
+ GError* error = nullptr;
+ gchar* cwd_utf8 = g_filename_to_utf8 ( cwd,
+ -1,
+ &bytesRead,
+ &bytesWritten,
+ &error);
+
+ inkscape_rel2abs (uri, cwd_utf8, full_path, 1000);
+ gchar *ret = g_strdup (full_path);
+ g_free (full_path);
+ g_free (cwd);
+ return ret;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vi: set autoindent shiftwidth=4 tabstop=8 filetype=cpp expandtab softtabstop=4 fileencoding=utf-8 textwidth=99 :
diff --git a/src/io/dir-util.h b/src/io/dir-util.h
new file mode 100644
index 0000000..91f07c8
--- /dev/null
+++ b/src/io/dir-util.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_DIR_UTIL_H
+#define SEEN_DIR_UTIL_H
+
+/*
+ * path-util.h
+ *
+ * here are functions sp_relative_path & cousins
+ * maybe they are already implemented in standard libs
+ *
+ */
+
+#include <cstdlib>
+#include <string>
+
+/**
+ * Returns a form of \a path relative to \a base if that is easy to construct (eg if \a path
+ * appears to be in the directory specified by \a base), otherwise returns \a path.
+ *
+ * @param path is expected to be an absolute path.
+ * @param base is expected to be either empty or the absolute path of a directory.
+ *
+ * @return a relative version of the path, if reasonable.
+ *
+ * @see inkscape_abs2rel for a more sophisticated version.
+ * @see prepend_current_dir_if_relative.
+*/
+std::string sp_relative_path_from_path(std::string const &path, std::string const &base);
+
+char const *sp_extension_from_path(char const *path);
+
+/**
+ * Convert a relative path name into absolute. If path is already absolute, does nothing except copying path to result.
+ *
+ * @param path relative path.
+ * @param base base directory (must be absolute path).
+ * @param result result buffer.
+ * @param size size of result buffer.
+ *
+ * @return != NULL: absolute path
+ * == NULL: error
+ *
+ * based on functions by Shigio Yamaguchi.
+ * FIXME:TODO: force it to also do path normalization of the entire resulting path,
+ * i.e. get rid of any .. and . in any place, even if 'path' is already absolute
+ * (now it returns it unchanged in this case)
+ *
+ */
+char *inkscape_rel2abs(char const *path, char const *base, char *result, size_t const size);
+
+char *inkscape_abs2rel(char const *path, char const *base, char *result, size_t const size);
+
+char *prepend_current_dir_if_relative(char const *filename);
+
+
+#endif // !SEEN_DIR_UTIL_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/doc2html.xsl b/src/io/doc2html.xsl
new file mode 100644
index 0000000..9a98c23
--- /dev/null
+++ b/src/io/doc2html.xsl
@@ -0,0 +1,64 @@
+<?xml version='1.0'?>
+<!-- SPDX-License-Identifier: GPL-2.0-or-later -->
+<xsl:stylesheet
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
+<xsl:output method="html"/>
+
+<xsl:template match="book">
+ <html>
+ <head>
+ <style TYPE="text/css">
+ p
+ {
+ text-indent: 0.25in
+ }
+ h3
+ {
+ font-family: Arial, Helvetica, 'Bitstream Vera Sans', 'Luxi Sans', Verdana, Sans-Serif;
+ font-size: 24px;
+ }
+ h4
+ {
+ font-family: Arial, Helvetica, 'Bitstream Vera Sans', 'Luxi Sans', Verdana, Sans-Serif;
+ font-size: 16px;
+ }
+
+ </style>
+ </head>
+ <body>
+ <xsl:apply-templates/>
+ </body>
+ </html>
+</xsl:template>
+
+<xsl:template match="bookinfo">
+ <xsl:apply-templates/>
+</xsl:template>
+
+<xsl:template match="title">
+ <h3>
+ <xsl:apply-templates/>
+ </h3>
+</xsl:template>
+
+<xsl:template match="author">
+ <h4>
+ <xsl:value-of select="./firstname"/>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="./surname"/>
+ </h4>
+</xsl:template>
+
+
+<xsl:template match="chapter">
+ <hr/>
+ <xsl:apply-templates/>
+</xsl:template>
+
+<xsl:template match="para">
+ <p>
+ <xsl:apply-templates/>
+ </p>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/io/file-export-cmd.cpp b/src/io/file-export-cmd.cpp
new file mode 100644
index 0000000..7670e50
--- /dev/null
+++ b/src/io/file-export-cmd.cpp
@@ -0,0 +1,786 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * File export from the command line. This code, greatly modified, use to be in main.cpp. It should
+ * be replaced by code shared with the file dialog (Gio::Actions?).
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * Git blame shows that bulia byak is the main author of the original export code from
+ * main.cpp. Other authors of note include Nicolas Dufour, Vinicius dos Santos Oliveira, and Bob
+ * Jamison; none of whom bothered to add their names to the copyright of main.cc.
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include "file-export-cmd.h"
+
+#include <png.h> // PNG export
+
+#include "document.h"
+#include "object/object-set.h"
+#include "object/sp-item.h"
+#include "object/sp-root.h"
+#include "object/sp-text.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-namedview.h"
+#include "object/sp-object-group.h"
+#include "path-chemistry.h" // sp_item_list_to_curves
+#include "text-editing.h" // te_update_layout_now_recursive
+#include "selection-chemistry.h" // fit_canvas_to_drawing
+#include "svg/svg-color.h" // Background color
+#include "helper/png-write.h" // PNG Export
+
+#include "extension/extension.h"
+#include "extension/system.h"
+#include "extension/db.h"
+#include "extension/output.h"
+#include "extension/init.h"
+
+InkFileExportCmd::InkFileExportCmd()
+ : export_overwrite(false)
+ , export_area_drawing(false)
+ , export_area_page(false)
+ , export_margin(0)
+ , export_area_snap(false)
+ , export_use_hints(false)
+ , export_width(0)
+ , export_height(0)
+ , export_dpi(0)
+ , export_ignore_filters(false)
+ , export_text_to_path(false)
+ , export_ps_level(3)
+ , export_pdf_level("1.5")
+ , export_latex(false)
+ , export_id_only(false)
+ , export_background_opacity(-1) // default is unset != actively set to 0
+ , export_plain_svg(false)
+{
+}
+
+void
+InkFileExportCmd::do_export(SPDocument* doc, std::string filename_in)
+{
+ std::string export_type_filename;
+ std::vector<Glib::ustring> export_type_list;
+
+ // Get export type from filename supplied with --export-filename
+ if (!export_filename.empty() && export_filename != "-") {
+ auto extension_pos = export_filename.find_last_of('.');
+ if (extension_pos == std::string::npos) {
+ if (export_type.empty()) {
+ std::cerr << "InkFileExportCmd::do_export: No export type specified. "
+ << "Append a supported file extension to filename provided with --export-filename or "
+ << "provide one or more extensions separately using --export-type" << std::endl;
+ return;
+ } else {
+ // no extension is fine if --export-type is given
+ }
+ } else {
+ export_type_filename = export_filename.substr(extension_pos+1);
+ export_filename.erase(extension_pos);
+ }
+ }
+
+ // Get export type(s) from string supplied with --export-type
+ if (!export_type.empty()) {
+ export_type_list = Glib::Regex::split_simple("[,;]", export_type);
+ }
+
+ // Determine actual type(s) for export.
+ if (export_use_hints) {
+ // Override type if --export-use-hints is used (hints presume PNG export for now)
+ // TODO: There's actually no reason to presume. We could allow to export to any format using hints!
+ if (export_id.empty() && !export_area_drawing) {
+ std::cerr << "InkFileExportCmd::do_export: "
+ << "--export-use-hints can only be used with --export-id or --export-area-drawing." << std::endl;
+ return;
+ }
+ if (export_type_list.size() > 1 || (export_type_list.size() == 1 && export_type_list[0] != "png")) {
+ std::cerr << "InkFileExportCmd::do_export: --export-use-hints can only be used with PNG export! "
+ << "Ignoring --export-type=" << export_type << "." << std::endl;
+ }
+ if (!export_filename.empty()) {
+ std::cerr << "InkFileExportCmd::do_export: --export-filename is ignored when using --export-use-hints!" << std::endl;
+ }
+ export_type_list.clear();
+ export_type_list.emplace_back("png");
+ } else if (export_type_list.empty()) {
+ if (!export_type_filename.empty()) {
+ export_type_list.emplace_back(export_type_filename); // use extension from filename
+ } else {
+ export_type_list.emplace_back("svg"); // fall-back to SVG by default
+ }
+ }
+
+ for (auto const& type: export_type_list) {
+ g_info("exporting '%s' to type '%s'", filename_in.c_str(), type.c_str());
+
+ export_type_current = type;
+
+ // Check for consistency between extension of --export-filename and --export-type if both are given
+ if (!export_type_filename.empty() && (type != export_type_filename)) {
+ std::cerr << "InkFileExportCmd::do_export: "
+ << "Ignoring extension of export filename (" << export_type_filename << ") "
+ << "as it does not match the current export type (" << type << ")." << std::endl;
+ }
+
+ if (type == "svg") {
+ do_export_svg(doc, filename_in);
+ } else if (type == "png") {
+ do_export_png(doc, filename_in);
+ } else if (type == "ps") {
+ do_export_ps_pdf(doc, filename_in, "image/x-postscript");
+ } else if (type == "eps") {
+ do_export_ps_pdf(doc, filename_in, "image/x-e-postscript");
+ } else if (type == "pdf") {
+ do_export_ps_pdf(doc, filename_in, "application/pdf");
+ } else if (type == "emf") {
+ do_export_win_metafile(doc, filename_in, "image/x-emf");
+ } else if (type == "wmf") {
+ do_export_win_metafile(doc, filename_in, "image/x-wmf");
+ } else if (type == "xaml") {
+ do_export_win_metafile(doc, filename_in, "text/xml+xaml");
+ } else {
+ std::cerr << "InkFileExportCmd::export: Unknown export type: " << type
+ << ". Allowed values: [svg,png,ps,eps,pdf,emf,wmf,xaml]." << std::endl;
+ }
+ }
+}
+
+
+// File names use std::string. HTML5 and presumably SVG 2 allows UTF-8 characters. Do we need to convert "object_id" here?
+std::string
+InkFileExportCmd::get_filename_out(std::string filename_in, std::string object_id)
+{
+ // Pipe out
+ if (export_filename == "-") {
+ return "-";
+ }
+
+ // Use filename provided as --export-filename if given (and append proper extension).
+ if (!export_filename.empty()) {
+ return export_filename + "." + export_type_current;
+ }
+
+ // Check for pipe
+ if (filename_in == "-") {
+ return "-";
+ }
+
+ // Construct output filename from input filename and export_type.
+ auto extension_pos = filename_in.find_last_of('.');
+ if (extension_pos == std::string::npos) {
+ std::cerr << "InkFileExportCmd::get_filename_out: cannot determine input file type from filename extension: " << filename_in << std::endl;
+ return (std::string());
+ }
+
+ std::string extension = filename_in.substr(extension_pos+1);
+ if (export_overwrite && export_type_current == extension) {
+ return filename_in;
+ } else {
+ std::string tag;
+ if (export_type_current == extension) {
+ tag = "_out";
+ }
+ if (!object_id.empty()) {
+ tag = "_" + object_id;
+ }
+ return (filename_in.substr(0,extension_pos) + tag + "." + export_type_current);
+ }
+
+ // We need a valid file name to write to unless we're using PNG export hints.
+ // if (!(export_type == "png" && export_use_hints)) {
+
+ // // Check for file name.
+ // if (filename_out.empty()) {
+ // std::cerr << "InkFileExportCmd::do_export: Could not determine output file name!" << std::endl;
+ // return (std::string());
+ // }
+
+ // // Check if directory exists.
+ // std::string directory = Glib::path_get_dirname(filename_out);
+ // if (!Glib::file_test(directory, Glib::FILE_TEST_IS_DIR)) {
+ // std::cerr << "InkFileExportCmd::do_export: File path includes directory that does not exist! " << directory << std::endl;
+ // return (std::string());
+ // }
+ // }
+}
+
+/**
+ * Perform an SVG export
+ *
+ * \param doc Document to export.
+ */
+int
+InkFileExportCmd::do_export_svg(SPDocument* doc, std::string filename_in)
+{
+ // Start with options that are once per document.
+ if (export_text_to_path) {
+ std::vector<SPItem*> items;
+ SPRoot *root = doc->getRoot();
+ doc->ensureUpToDate();
+ for (auto& iter: root->children) {
+ SPItem* item = (SPItem*) &iter;
+ if (! (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item) || SP_IS_GROUP(item))) {
+ continue;
+ }
+
+ te_update_layout_now_recursive(item);
+ items.push_back(item);
+ }
+
+ std::vector<SPItem*> selected; // Not used
+ std::vector<Inkscape::XML::Node*> to_select; // Not used
+
+ sp_item_list_to_curves(items, selected, to_select);
+ }
+
+ if (export_margin != 0) {
+ gdouble margin = export_margin;
+ doc->ensureUpToDate();
+ SPNamedView *nv;
+ Inkscape::XML::Node *nv_repr;
+ if ((nv = sp_document_namedview(doc, nullptr)) && (nv_repr = nv->getRepr())) {
+ sp_repr_set_svg_double(nv_repr, "fit-margin-top", margin);
+ sp_repr_set_svg_double(nv_repr, "fit-margin-left", margin);
+ sp_repr_set_svg_double(nv_repr, "fit-margin-right", margin);
+ sp_repr_set_svg_double(nv_repr, "fit-margin-bottom", margin);
+ }
+ }
+
+ if (export_area_drawing) {
+ fit_canvas_to_drawing(doc, export_margin != 0 ? true : false);
+ } else if (export_area_page || export_id.empty() ) {
+ if (export_margin) {
+ doc->ensureUpToDate();
+ doc->fitToRect(*(doc->preferredBounds()), true);
+ }
+ }
+
+
+ // Export each object in list (or root if empty). Use ';' so in future it could be possible to selected multiple objects to export together.
+ std::vector<Glib::ustring> objects = Glib::Regex::split_simple("\\s*;\\s*", export_id);
+ if (objects.empty()) {
+ objects.emplace_back(); // So we do loop at least once for root.
+ }
+
+ for (auto object : objects) {
+
+ std::string filename_out = get_filename_out(filename_in, object);
+ if (filename_out.empty()) {
+ return 1;
+ }
+
+ if(!object.empty()) {
+ doc->ensureUpToDate();
+
+ // "crop" the document to the specified object, cleaning as we go.
+ SPObject *obj = doc->getObjectById(object);
+ if (obj == nullptr) {
+ std::cerr << "InkFileExportCmd::do_export_svg: Object " << object << " not found in document, nothing to export." << std::endl;
+ return 1;
+ }
+ if (export_id_only) {
+ // If -j then remove all other objects to complete the "crop"
+ doc->getRoot()->cropToObject(obj);
+ }
+ if (!(export_area_page || export_area_drawing)) {
+ Inkscape::ObjectSet s(doc);
+ s.set(obj);
+ s.fitCanvas(export_margin ? true : false);
+ }
+ }
+
+ if (export_plain_svg) {
+
+ try {
+ Inkscape::Extension::save(Inkscape::Extension::db.get("org.inkscape.output.svg.plain"), doc, filename_out.c_str(), false,
+ false, false, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY);
+ } catch (Inkscape::Extension::Output::save_failed &e) {
+ std::cerr << "InkFileExportCmd::do_export_svg: Failed to save SVG to: " << filename_out << std::endl;
+ return 1;
+ }
+
+ } else {
+
+ // Export as inkscape SVG.
+ try {
+ Inkscape::Extension::save(Inkscape::Extension::db.get("org.inkscape.output.svg.inkscape"), doc, filename_out.c_str(), false,
+ false, false, Inkscape::Extension::FILE_SAVE_METHOD_INKSCAPE_SVG);
+ } catch (Inkscape::Extension::Output::save_failed &e) {
+ std::cerr << "InkFileExportCmd::do_export_svg: Failed to save Inkscape SVG to: " << filename_out << std::endl;
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+guint32 InkFileExportCmd::get_bgcolor(SPDocument *doc) {
+ guint32 bgcolor = 0x00000000;
+ if (!export_background.empty()) {
+ // override the page color
+ bgcolor = sp_svg_read_color(export_background.c_str(), 0xffffff00);
+ // default is opaque if a color is given on commandline
+ if (export_background_opacity < -.5 ) {
+ export_background_opacity = 255;
+ }
+ } else {
+ // read from namedview
+ Inkscape::XML::Node *nv = doc->getReprNamedView();
+ if (nv && nv->attribute("pagecolor")){
+ bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00);
+ }
+ }
+
+ if (export_background_opacity > -.5) { // if the value is manually set
+ if (export_background_opacity > 1.0) {
+ float value = CLAMP (export_background_opacity, 1.0f, 255.0f);
+ bgcolor |= (guint32) floor(value);
+ } else {
+ float value = CLAMP (export_background_opacity, 0.0f, 1.0f);
+ bgcolor |= SP_COLOR_F_TO_U(value);
+ }
+ } else {
+ Inkscape::XML::Node *nv = doc->getReprNamedView();
+ if (nv && nv->attribute("inkscape:pageopacity")){
+ double opacity = 1.0;
+ sp_repr_get_double (nv, "inkscape:pageopacity", &opacity);
+ bgcolor |= SP_COLOR_F_TO_U(opacity);
+ } // else it's transparent
+ }
+ return bgcolor;
+}
+
+/**
+ * Perform a PNG export
+ *
+ * \param doc Document to export.
+ */
+int
+InkFileExportCmd::do_export_png(SPDocument *doc, std::string filename_in)
+{
+ bool filename_from_hint = false;
+ gdouble dpi = 0.0;
+ guint32 bgcolor = get_bgcolor(doc);
+
+ // Export each object in list (or root if empty). Use ';' so in future it could be possible to selected multiple objects to export together.
+ std::vector<Glib::ustring> objects = Glib::Regex::split_simple("\\s*;\\s*", export_id);
+ if (objects.empty()) {
+ objects.emplace_back(); // So we do loop at least once for root.
+ }
+
+ for (auto object_id : objects) {
+
+ std::string filename_out = get_filename_out(filename_in, object_id);
+
+ std::vector<SPItem*> items;
+
+ // Find export object. (Either root or object with specified id.)
+ SPObject *object = doc->getRoot();
+ if (!object_id.empty()) {
+ object = doc->getObjectById(object_id);
+ }
+
+ if (!object) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Object with id=\"" << object_id
+ << "\" was not found in the document. Skipping." << std::endl;
+ continue;
+ }
+
+ if (!SP_IS_ITEM (object)) {
+ std::cerr << "InkFileExportCmd::do_export_png: "
+ << "Object with id=\"" << object_id
+ << "\" is not a visible item. Skipping." << std::endl;
+ continue;
+ }
+
+ items.push_back(SP_ITEM(object)); // There is only one item, why do this?
+
+ if (export_id_only) {
+ std::cerr << "Exporting only object with id=\""
+ << object_id << "\"; all other objects hidden." << std::endl;
+ }
+
+ // Find file name and dpi from hints.
+ if (export_use_hints) {
+
+ // Retrieve export filename hint.
+ const gchar *fn_hint = object->getRepr()->attribute("inkscape:export-filename");
+ if (fn_hint) {
+ filename_out = fn_hint;
+ filename_from_hint = true;
+ } else {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Export filename hint not found for object " << object_id << ". Skipping." << std::endl;
+ continue;
+ }
+
+ // Retrieve export dpi hint. Only xdpi as ydpi is always the same now.
+ const gchar *dpi_hint = object->getRepr()->attribute("inkscape:export-xdpi");
+ if (dpi_hint) {
+ if (export_dpi || export_width || export_height) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Using bitmap dimensions from the command line "
+ << "(--export-dpi, --export-width, or --export-height). "
+ << "DPI hint " << dpi_hint << " is ignored." << std::endl;
+ } else {
+ dpi = atof(dpi_hint);
+ }
+ } else {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Export DPI hint not found for the object." << std::endl;
+ }
+ }
+
+ // ------------------------- File name -------------------------
+
+ // Check we have a filename.
+ if (filename_out.empty()) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "No valid export filename given and no filename hint. Skipping." << std::endl;
+ continue;
+ }
+
+ if (filename_from_hint) {
+ //Make relative paths go from the document location, if possible:
+ if (!Glib::path_is_absolute(filename_out) && doc->getDocumentURI()) {
+ std::string dirname = Glib::path_get_dirname(doc->getDocumentURI());
+ if (!dirname.empty()) {
+ filename_out = Glib::build_filename(dirname, filename_out);
+ }
+ }
+ }
+
+ // Check if directory exists
+ std::string directory = Glib::path_get_dirname(filename_out);
+ if (!Glib::file_test(directory, Glib::FILE_TEST_IS_DIR)) {
+ std::cerr << "File path " << filename_out << " includes directory that doesn't exist. Skipping." << std::endl;
+ continue;
+ }
+
+ // -------------------------- DPI -------------------------------
+
+ if (export_dpi != 0.0 && dpi == 0.0) {
+ dpi = export_dpi;
+ if ((dpi < 0.1) || (dpi > 10000.0)) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "DPI value " << export_dpi
+ << " out of range [0.1 - 10000.0]. Skipping.";
+ continue;
+ }
+ }
+
+ // default dpi
+ if (dpi == 0.0) {
+ dpi = Inkscape::Util::Quantity::convert(1, "in", "px");
+ }
+
+ // ------------------------- Area -------------------------------
+
+ Geom::Rect area;
+ doc->ensureUpToDate();
+
+ // Three choices: 1. Command-line export_area 2. Page area 3. Drawing area
+ if (!export_area.empty()) {
+
+ // Export area command-line
+
+ /* Try to parse area (given in SVG pixels) */
+ gdouble x0,y0,x1,y1;
+ if (sscanf(export_area.c_str(), "%lg:%lg:%lg:%lg", &x0, &y0, &x1, &y1) != 4) {
+ g_warning("Cannot parse export area '%s'; use 'x0:y0:x1:y1'. Nothing exported.", export_area.c_str());
+ return 1; // If it fails once, it will fail for all objects.
+ }
+ area = Geom::Rect(Geom::Interval(x0,x1), Geom::Interval(y0,y1));
+
+ } else if (export_area_page || (!export_area_drawing && object_id.empty())) {
+
+ // Export area page (explicit or if no object is given).
+ Geom::Point origin(doc->getRoot()->x.computed, doc->getRoot()->y.computed);
+ area = Geom::Rect(origin, origin + doc->getDimensions());
+
+ } else {
+
+ // Export area drawing (explicit or if object is given).
+ Geom::OptRect areaMaybe = static_cast<SPItem *>(object)->documentVisualBounds();
+ if (areaMaybe) {
+ area = *areaMaybe;
+ } else {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Unable to determine a valid bounding box. Skipping." << std::endl;
+ continue;
+ }
+ }
+
+ if (export_area_snap) {
+ area = area.roundOutwards();
+ }
+ // End finding area.
+
+ // -------------------------- Width and Height ---------------------------------
+
+ unsigned long int width = 0;
+ unsigned long int height = 0;
+ double xdpi = dpi;
+ double ydpi = dpi;
+
+ if (export_height != 0) {
+ height = export_height;
+ if ((height < 1) || (height > PNG_UINT_31_MAX)) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Export height " << height << " out of range (1 to " << PNG_UINT_31_MAX << ")" << std::endl;
+ continue;
+ }
+ ydpi = Inkscape::Util::Quantity::convert(height, "in", "px") / area.height();
+ xdpi = ydpi;
+ dpi = ydpi;
+ }
+
+ if (export_width != 0) {
+ width = export_width;
+ if ((width < 1) || (width > PNG_UINT_31_MAX)) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Export width " << width << " out of range (1 to " << PNG_UINT_31_MAX << ")." << std::endl;
+ continue;
+ }
+ xdpi = Inkscape::Util::Quantity::convert(width, "in", "px") / area.width();
+ ydpi = export_height ? ydpi : xdpi;
+ dpi = xdpi;
+ }
+
+ if (width == 0) {
+ width = (unsigned long int) (Inkscape::Util::Quantity::convert(area.width(), "px", "in") * dpi + 0.5);
+ }
+
+ if (height == 0) {
+ height = (unsigned long int) (Inkscape::Util::Quantity::convert(area.height(), "px", "in") * dpi + 0.5);
+ }
+
+ if ((width < 1) || (height < 1) || (width > PNG_UINT_31_MAX) || (height > PNG_UINT_31_MAX)) {
+ std::cerr << "InkFileExport::do_export_png: Dimensions " << width << "x" << height << " are out of range (1 to " << PNG_UINT_31_MAX << ")." << std::endl;
+ continue;
+ }
+
+ // ---------------------- Generate the PNG -------------------------------
+
+ // Do we really need to print this?
+ std::cerr << "Background RRGGBBAA: " << std::hex << bgcolor << std::dec << std::endl;
+ std::cerr << "Area "
+ << area[Geom::X][0] << ":" << area[Geom::Y][0] << ":"
+ << area[Geom::X][1] << ":" << area[Geom::Y][1] << " exported to "
+ << width << " x " << height << " pixels (" << dpi << " dpi)" << std::endl;
+
+ reverse(items.begin(),items.end()); // But there was only one item!
+
+ if( sp_export_png_file(doc, filename_out.c_str(), area, width, height, xdpi, ydpi,
+ bgcolor, nullptr, nullptr, true, export_id_only ? items : std::vector<SPItem*>()) == 1 ) {
+ } else {
+ std::cerr << "InkFileExport::do_export_png: Failed to export to " << filename_out << std::endl;
+ continue;
+ }
+
+ } // End loop over objects.
+ return 0;
+}
+
+
+/**
+ * Perform a PDF/PS/EPS export
+ *
+ * \param doc Document to export.
+ * \param filename File to write to.
+ * \param mime MIME type to export as.
+ */
+int
+InkFileExportCmd::do_export_ps_pdf(SPDocument* doc, std::string filename_in, std::string mime_type)
+{
+ // Check if we support mime type.
+ Inkscape::Extension::DB::OutputList o;
+ Inkscape::Extension::db.get_output_list(o);
+ Inkscape::Extension::DB::OutputList::const_iterator i = o.begin();
+ while (i != o.end() && strcmp( (*i)->get_mimetype(), mime_type.c_str() ) != 0) {
+ i++;
+ }
+
+ if (i == o.end()) {
+ std::cerr << "InkFileExportCmd::do_export_ps_pdf: Could not find an extension to export to MIME type: " << mime_type << std::endl;
+ return 1;
+ }
+
+ // Start with options that are once per document.
+
+ // Set export options.
+ if (export_text_to_path) {
+ (*i)->set_param_optiongroup("textToPath", "paths");
+ } else if (export_latex) {
+ (*i)->set_param_optiongroup("textToPath", "LaTeX");
+ } else {
+ (*i)->set_param_optiongroup("textToPath", "embed");
+ }
+
+ if (export_ignore_filters) {
+ (*i)->set_param_bool("blurToBitmap", false);
+ } else {
+ (*i)->set_param_bool("blurToBitmap", true);
+
+ gdouble dpi = 96.0;
+ if (export_dpi) {
+ dpi = export_dpi;
+ if ((dpi < 1) || (dpi > 10000.0)) {
+ g_warning("DPI value %lf out of range [1 - 10000]. Using 96 dpi instead.", export_dpi);
+ dpi = 96;
+ }
+ }
+
+ (*i)->set_param_int("resolution", (int) dpi);
+ }
+
+ (*i)->set_param_float("bleed", export_margin);
+
+ // handle --export-pdf-version
+ if (mime_type == "application/pdf") {
+ bool set_export_pdf_version_fail = true;
+ const gchar *pdfver_param_name = "PDFversion";
+ if (!export_pdf_level.empty()) {
+ // combine "PDF " and the given command line
+ std::string version_gui_string = std::string("PDF-") + export_pdf_level;
+ try {
+ // first, check if the given pdf version is selectable in the ComboBox
+ if ((*i)->get_param_optiongroup_contains("PDFversion", version_gui_string.c_str())) {
+ (*i)->set_param_optiongroup(pdfver_param_name, version_gui_string.c_str());
+ set_export_pdf_version_fail = false;
+ } else {
+ g_warning("Desired PDF export version \"%s\" not supported! Hint: input one of the versions found in the pdf export dialog e.g. \"1.4\".",
+ export_pdf_level.c_str());
+ }
+ } catch (...) {
+ // can be thrown along the way:
+ // throw Extension::param_not_exist();
+ // throw Extension::param_not_enum_param();
+ g_warning("Parameter or Enum \"%s\" might not exist", pdfver_param_name);
+ }
+ }
+
+ // set default pdf export version to 1.4, also if something went wrong
+ if(set_export_pdf_version_fail) {
+ (*i)->set_param_optiongroup(pdfver_param_name, "PDF-1.4");
+ }
+ }
+
+ if (mime_type == "image/x-postscript" || mime_type == "image/x-e-postscript") {
+ if ( export_ps_level < 2 || export_ps_level > 3 ) {
+ g_warning("Only supported PostScript levels are 2 and 3."
+ " Defaulting to 2.");
+ export_ps_level = 2;
+ }
+
+ (*i)->set_param_optiongroup("PSlevel", (export_ps_level == 3) ? "PS3" : "PS2");
+ }
+
+
+ // Export each object in list (or root if empty). Use ';' so in future it could be possible to selected multiple objects to export together.
+ std::vector<Glib::ustring> objects = Glib::Regex::split_simple("\\s*;\\s*", export_id);
+ if (objects.empty()) {
+ objects.emplace_back(); // So we do loop at least once for root.
+ }
+
+ for (auto object : objects) {
+
+ std::string filename_out = get_filename_out(filename_in, object);
+ if (filename_out.empty()) {
+ return 1;
+ }
+
+ // Export only object with given id.
+ if (!object.empty()) {
+ SPObject *o = doc->getObjectById(object);
+ if (o == nullptr) {
+ std::cerr << "InkFileExportCmd::do_export_ps_pdf: Object " << object << " not found in document, nothing to export." << std::endl;
+ return 1;
+ }
+ (*i)->set_param_string ("exportId", object.c_str());
+ } else {
+ (*i)->set_param_string ("exportId", "");
+ }
+
+ // Set export area.
+ if (export_area_page && export_area_drawing) {
+ std::cerr << "You cannot use --export-area-page and --export-area-drawing at the same time; only the former will take effect." << std::endl;;
+ export_area_drawing = false;
+ }
+
+ if (export_area_drawing) {
+ (*i)->set_param_optiongroup ("area", "drawing");
+ }
+
+ if (export_area_page) {
+ if (export_type == "eps") {
+ std::cerr << "EPS cannot have its bounding box extend beyond its content, so if your drawing is smaller than the page, --export-area-page will clip it to drawing." << std::endl;
+ }
+ (*i)->set_param_optiongroup ("area", "page");
+ }
+
+ if (!export_area_drawing && !export_area_page) {
+ // Neither is set.
+ if (export_type == "eps" || !object.empty()) {
+ // Default to drawing for EPS or if object is specified (latter matches behavior for other export types).
+ (*i)->set_param_optiongroup("area", "drawing");
+ } else {
+ (*i)->set_param_optiongroup("area", "page");
+ }
+ }
+
+ try {
+ (*i)->save(doc, filename_out.c_str());
+ } catch(...) {
+ std::cerr << "Failed to save PS/EPS/PDF to: " << filename_out << std::endl;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Export a document to EMF or WMF
+ *
+ * \param doc Document to export.
+ * \param filename to export to.
+ * \param mime MIME type to export as (should be "image/x-emf" or "image/x-wmf")
+ */
+int
+InkFileExportCmd::do_export_win_metafile(SPDocument* doc, std::string filename_in, std::string mime_type)
+{
+ std::string filename_out = get_filename_out(filename_in);
+
+ // Check if we support mime type.
+ Inkscape::Extension::DB::OutputList o;
+ Inkscape::Extension::db.get_output_list(o);
+ Inkscape::Extension::DB::OutputList::const_iterator i = o.begin();
+ while (i != o.end() && strcmp( (*i)->get_mimetype(), mime_type.c_str() ) != 0) {
+ i++;
+ }
+
+ if (i == o.end())
+ {
+ std::cerr << "InkFileExportCmd::do_export_win_metafile_common: Could not find an extension to export to MIME type: " << mime_type << std::endl;
+ return 1;
+ }
+
+ (*i)->save(doc, filename_out.c_str());
+ return 0;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/io/file-export-cmd.h b/src/io/file-export-cmd.h
new file mode 100644
index 0000000..6d63ccb
--- /dev/null
+++ b/src/io/file-export-cmd.h
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * File export from the command line. This code use to be in main.cpp. It should be
+ * replaced by shared code (Gio::Actions?) for export from the file dialog.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_FILE_EXPORT_CMD_H
+#define INK_FILE_EXPORT_CMD_H
+
+#include <iostream>
+#include <gtkmm.h>
+
+class SPDocument;
+
+class InkFileExportCmd {
+
+public:
+ InkFileExportCmd();
+
+ void do_export(SPDocument* doc, std::string filename_in="");
+
+private:
+
+ guint32 get_bgcolor(SPDocument *doc);
+ std::string get_filename_out(std::string filename_in="", std::string object_id="");
+ int do_export_svg( SPDocument* doc, std::string filename_in);
+ int do_export_png( SPDocument* doc, std::string filename_in);
+ int do_export_ps_pdf(SPDocument* doc, std::string filename_in, std::string mime_type);
+ int do_export_win_metafile(SPDocument* doc, std::string filename_in, std::string mime_type);
+
+ Glib::ustring export_type_current;
+
+public:
+ // Should be private, but this is just temporary code (I hope!).
+
+ // One-to-one correspondence with command line options
+ std::string export_filename; // Only if one file is processed!
+
+ Glib::ustring export_type;
+ bool export_overwrite;
+
+ Glib::ustring export_area;
+ bool export_area_drawing;
+ bool export_area_page;
+ int export_margin;
+ bool export_area_snap;
+ int export_width;
+ int export_height;
+
+ double export_dpi;
+ bool export_ignore_filters;
+ bool export_text_to_path;
+ int export_ps_level;
+ Glib::ustring export_pdf_level;
+ bool export_latex;
+ Glib::ustring export_id;
+ bool export_id_only;
+ bool export_use_hints;
+ Glib::ustring export_background;
+ double export_background_opacity;
+ bool export_plain_svg;
+};
+
+#endif // INK_FILE_EXPORT_CMD_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/io/file.cpp b/src/io/file.cpp
new file mode 100644
index 0000000..a4b0699
--- /dev/null
+++ b/src/io/file.cpp
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * File operations (independent of GUI)
+ *
+ * Copyright (C) 2018, 2019 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include "file.h"
+
+#include <iostream>
+#include <gtkmm.h>
+
+#include "document.h"
+#include "document-undo.h"
+
+#include "extension/system.h" // Extension::open()
+#include "extension/extension.h"
+#include "extension/db.h"
+#include "extension/output.h"
+#include "extension/input.h"
+
+#include "object/sp-root.h"
+
+#include "xml/repr.h"
+
+
+/**
+ * Create a blank document, remove any template data.
+ * Input: Empty string or template file name.
+ */
+SPDocument*
+ink_file_new(const std::string &Template)
+{
+ SPDocument *doc = SPDocument::createNewDoc ((Template.empty() ? nullptr : Template.c_str()), true, true );
+
+ if (doc) {
+ // Remove all the template info from xml tree
+ Inkscape::XML::Node *myRoot = doc->getReprRoot();
+ Inkscape::XML::Node *nodeToRemove;
+
+ nodeToRemove = sp_repr_lookup_name(myRoot, "inkscape:templateinfo");
+ if (nodeToRemove != nullptr) {
+ Inkscape::DocumentUndo::ScopedInsensitive no_undo(doc);
+ sp_repr_unparent(nodeToRemove);
+ delete nodeToRemove;
+ }
+ nodeToRemove = sp_repr_lookup_name(myRoot, "inkscape:_templateinfo"); // backwards-compatibility
+ if (nodeToRemove != nullptr) {
+ Inkscape::DocumentUndo::ScopedInsensitive no_undo(doc);
+ sp_repr_unparent(nodeToRemove);
+ delete nodeToRemove;
+ }
+ } else {
+ std::cout << "ink_file_new: Did not create new document!" << std::endl;
+ }
+
+ return doc;
+}
+
+/**
+ * Open a document from memory.
+ */
+SPDocument*
+ink_file_open(const Glib::ustring& data)
+{
+ SPDocument *doc = SPDocument::createNewDocFromMem (data.c_str(), data.length(), true);
+
+ if (doc == nullptr) {
+ std::cerr << "ink_file_open: cannot open file in memory (pipe?)" << std::endl;
+ } else {
+
+ // This is the only place original values should be set.
+ SPRoot *root = doc->getRoot();
+ root->original.inkscape = root->version.inkscape;
+ root->original.svg = root->version.svg;
+ }
+
+ return doc;
+}
+
+/**
+ * Open a document.
+ */
+SPDocument*
+ink_file_open(const Glib::RefPtr<Gio::File>& file, bool *cancelled_param)
+{
+ bool cancelled = false;
+
+ SPDocument *doc = nullptr;
+
+ std::string path = file->get_path();
+
+ // TODO: It's useless to catch these exceptions here (and below) unless we do something with them.
+ // If we can't properly handle them (e.g. by showing a user-visible message) don't catch them!
+ try {
+ doc = Inkscape::Extension::open(nullptr, path.c_str());
+ } catch (Inkscape::Extension::Input::no_extension_found &e) {
+ doc = nullptr;
+ } catch (Inkscape::Extension::Input::open_failed &e) {
+ doc = nullptr;
+ } catch (Inkscape::Extension::Input::open_cancelled &e) {
+ cancelled = true;
+ doc = nullptr;
+ }
+
+ // Try to open explicitly as SVG.
+ // TODO: Why is this necessary? Shouldn't this be handled by the first call already?
+ if (doc == nullptr && !cancelled) {
+ try {
+ doc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), path.c_str());
+ } catch (Inkscape::Extension::Input::no_extension_found &e) {
+ doc = nullptr;
+ } catch (Inkscape::Extension::Input::open_failed &e) {
+ doc = nullptr;
+ } catch (Inkscape::Extension::Input::open_cancelled &e) {
+ cancelled = true;
+ doc = nullptr;
+ }
+ }
+
+ if (doc != nullptr) {
+ // This is the only place original values should be set.
+ SPRoot *root = doc->getRoot();
+ root->original.inkscape = root->version.inkscape;
+ root->original.svg = root->version.svg;
+ } else if (!cancelled) {
+ std::cerr << "ink_file_open: '" << path << "' cannot be opened!" << std::endl;
+ }
+
+ if (cancelled_param) {
+ *cancelled_param = cancelled;
+ }
+ return doc;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/io/file.h b/src/io/file.h
new file mode 100644
index 0000000..5d2ec6d
--- /dev/null
+++ b/src/io/file.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * File operations (independent of GUI)
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_FILE_IO_H
+#define INK_FILE_IO_H
+
+#include <string>
+
+namespace Gio {
+class File;
+}
+
+namespace Glib {
+class ustring;
+
+template <class T>
+class RefPtr;
+}
+
+class SPDocument;
+
+SPDocument* ink_file_new(const std::string &Template = nullptr);
+SPDocument* ink_file_open(const Glib::ustring &data);
+SPDocument* ink_file_open(const Glib::RefPtr<Gio::File>& file, bool *cancelled = nullptr);
+
+// To do:
+// ink_file_save()
+// ink_file_export()
+// ink_file_import()
+
+
+
+#endif // INK_FILE_IO_H
diff --git a/src/io/http.cpp b/src/io/http.cpp
new file mode 100644
index 0000000..6f28db7
--- /dev/null
+++ b/src/io/http.cpp
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Inkscape::IO::HTTP - make internet requests using libsoup
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+/*
+ * How to use:
+ *
+ * #include "io/http.cpp"
+ * void _async_test_call(Glib::ustring filename) {
+ * g_warning("HTTP request saved to %s", filename.c_str());
+ * }
+ * uint timeout = 20 * 60 * 60; // 20 hours
+ * Glib::ustring filename = Inkscape::IO::HTTP::get_file("https://media.inkscape.org/media/messages.xml", timeout, _async_test_call);
+ *
+ */
+
+#include <glib/gstdio.h>
+#include <libsoup/soup.h>
+#include <string>
+#include <ctime>
+
+#include "io/sys.h"
+#include "io/http.h"
+#include "io/resource.h"
+
+typedef std::function<void(Glib::ustring)> callback;
+
+namespace Resource = Inkscape::IO::Resource;
+
+namespace Inkscape {
+namespace IO {
+namespace HTTP {
+
+void _save_data_as_file(Glib::ustring filename, const char *result) {
+ FILE *fileout = Inkscape::IO::fopen_utf8name(filename.c_str(), "wb");
+ if (!fileout) {
+ g_warning("HTTP Cache: Can't open %s for write.", filename.c_str());
+ return;
+ }
+
+ fputs(result, fileout);
+ fflush(fileout);
+ if (ferror(fileout)) {
+ g_warning("HTTP Cache: Error writing data to %s.", filename.c_str());
+ }
+
+ fclose(fileout);
+}
+
+void _get_file_callback(SoupSession *session, SoupMessage *msg, gpointer user_data) {
+ auto data = static_cast<std::pair<callback, Glib::ustring>*>(user_data);
+ data->first(data->second);
+ delete data;
+}
+
+/*
+ * Downloads a file, caches it in the local filesystem and then returns the
+ * new filename of the cached file.
+ *
+ * Returns: filename where the file has been (blocking) or will be (async) stored.
+ *
+ * uri - The uri of the desired resource, the filename comes from this uri
+ * timeout - Number of seconds to keep the cache, default is forever
+ * func - An optional function, if given the function becomes asynchronous
+ * the returned filename will be where the file 'hopes' to be saved
+ * and this function will be called when the http request is complete.
+ *
+ * NOTE: If the cache file exists, then it's returned without any async request
+ * your func will be called in a blocking way BEFORE this function returns.
+ *
+ */
+Glib::ustring get_file(Glib::ustring uri, unsigned int timeout, callback func) {
+
+ SoupURI *s_uri = soup_uri_new(uri.c_str());
+ std::string path = std::string(soup_uri_decode(soup_uri_get_path(s_uri)));
+ std::string filepart;
+
+ // Parse the url into a filename suitable for caching.
+ if(path.back() == '/') {
+ filepart = path.replace(path.begin(), path.end(), '/', '_');
+ filepart += ".url";
+ } else {
+ filepart = path.substr(path.rfind("/") + 1);
+ }
+
+ const char *ret = get_path(Resource::CACHE, Resource::NONE, filepart.c_str());
+ Glib::ustring filename = Glib::ustring(ret);
+
+ // We test first if the cache already exists
+ if(file_test(filename.c_str(), G_FILE_TEST_EXISTS) && timeout > 0) {
+ GStatBuf st;
+ if(g_stat(filename.c_str(), &st) != -1) {
+ time_t changed = st.st_mtime;
+ time_t now = time(nullptr);
+ // The cache hasn't timed out, so return the filename.
+ if(now - changed < timeout) {
+ if(func) {
+ // Non-async func callback return, may block.
+ func(filename);
+ }
+ return filename;
+ }
+ g_debug("HTTP Cache is stale: %s", filename.c_str());
+ }
+ }
+
+ // Only then do we get the http request
+ SoupMessage *msg = soup_message_new_from_uri("GET", s_uri);
+ SoupSession *session = soup_session_new();
+
+#ifdef DEBUG_HTTP
+ SoupLogger *logger;
+ logger = soup_logger_new(SOUP_LOGGER_LOG_BODY, -1);
+ soup_session_add_feature(session, SOUP_SESSION_FEATURE (logger));
+ g_object_unref (logger);
+#endif
+
+ if(func) {
+ auto *user_data = new std::pair<callback, Glib::ustring>(func, filename);
+ soup_session_queue_message(session, msg, _get_file_callback, user_data);
+ } else {
+ guint status = soup_session_send_message (session, msg);
+ if(status == SOUP_STATUS_OK) {
+ g_debug("HTTP Cache saved to: %s", filename.c_str());
+ _save_data_as_file(filename, msg->response_body->data);
+ } else {
+ g_warning("Can't download %s", uri.c_str());
+ }
+ }
+ return filename;
+}
+
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/http.h b/src/io/http.h
new file mode 100644
index 0000000..398d699
--- /dev/null
+++ b/src/io/http.h
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Inkscape::IO::HTTP - make internet requests using libsoup
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_IO_HTTP_H
+#define SEEN_INKSCAPE_IO_HTTP_H
+
+#include <functional>
+#include <glibmm/ustring.h>
+
+/**
+ * simple libsoup based resource API
+ */
+
+namespace Inkscape {
+namespace IO {
+namespace HTTP {
+
+ Glib::ustring get_file(Glib::ustring uri, unsigned int timeout=0, std::function<void(Glib::ustring)> func=nullptr);
+
+}
+}
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/resource-manager.cpp b/src/io/resource-manager.cpp
new file mode 100644
index 0000000..0fe346a
--- /dev/null
+++ b/src/io/resource-manager.cpp
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::ResourceManager - tracks external resources such as image and css files.
+ *
+ * Copyright 2011 Jon A. Cruz <jon@joncruz.org>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <string>
+#include <vector>
+#include <set>
+#include <algorithm>
+
+#include <gtkmm/recentmanager.h>
+#include <glibmm/i18n.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/fileutils.h>
+#include <glibmm/uriutils.h>
+#include <glibmm/convert.h>
+
+#include "resource-manager.h"
+
+#include "document.h"
+#include "document-undo.h"
+#include "verbs.h"
+
+#include "object/sp-object.h"
+
+#include "xml/node.h"
+
+namespace Inkscape {
+
+static std::vector<std::string> splitPath( std::string const &path )
+{
+ std::vector<std::string> parts;
+
+ std::string prior;
+ std::string tmp = path;
+ while ( !tmp.empty() && (tmp != prior) ) {
+ prior = tmp;
+
+ parts.push_back( Glib::path_get_basename(tmp) );
+ tmp = Glib::path_get_dirname(tmp);
+ }
+ if ( !parts.empty() ) {
+ std::reverse(parts.begin(), parts.end());
+ if ( (parts[0] == ".") && (path[0] != '.') ) {
+ parts.erase(parts.begin());
+ }
+ }
+
+ return parts;
+}
+
+static std::string convertPathToRelative( std::string const &path, std::string const &docbase )
+{
+ std::string result = path;
+
+ if ( !path.empty() && Glib::path_is_absolute(path) ) {
+ // Whack the parts into pieces
+
+ std::vector<std::string> parts = splitPath(path);
+ std::vector<std::string> baseParts = splitPath(docbase);
+
+ // TODO debug g_message("+++++++++++++++++++++++++");
+ for ( std::vector<std::string>::iterator it = parts.begin(); it != parts.end(); ++it ) {
+ // TODO debug g_message(" [%s]", it->c_str());
+ }
+ // TODO debug g_message(" - - - - - - - - - - - - - - - ");
+ for ( std::vector<std::string>::iterator it = baseParts.begin(); it != baseParts.end(); ++it ) {
+ // TODO debug g_message(" [%s]", it->c_str());
+ }
+ // TODO debug g_message("+++++++++++++++++++++++++");
+
+ if ( !parts.empty() && !baseParts.empty() && (parts[0] == baseParts[0]) ) {
+ // Both paths have the same root. We can proceed.
+ while ( !parts.empty() && !baseParts.empty() && (parts[0] == baseParts[0]) ) {
+ parts.erase( parts.begin() );
+ baseParts.erase( baseParts.begin() );
+ }
+
+ // TODO debug g_message("+++++++++++++++++++++++++");
+ for ( std::vector<std::string>::iterator it = parts.begin(); it != parts.end(); ++it ) {
+ // TODO debug g_message(" [%s]", it->c_str());
+ }
+ // TODO debug g_message(" - - - - - - - - - - - - - - - ");
+ for ( std::vector<std::string>::iterator it = baseParts.begin(); it != baseParts.end(); ++it ) {
+ // TODO debug g_message(" [%s]", it->c_str());
+ }
+ // TODO debug g_message("+++++++++++++++++++++++++");
+
+ if ( !parts.empty() ) {
+ result.clear();
+
+ for ( size_t i = 0; i < baseParts.size(); ++i ) {
+ parts.insert(parts.begin(), "..");
+ }
+ result = Glib::build_filename( parts );
+ // TODO debug g_message("----> [%s]", result.c_str());
+ }
+ }
+ }
+
+ return result;
+}
+
+
+class ResourceManagerImpl : public ResourceManager {
+public:
+ ResourceManagerImpl();
+ ~ResourceManagerImpl() override;
+
+ bool fixupBrokenLinks(SPDocument *doc) override;
+
+
+ /**
+ * Walk all links in a document and create a listing of unique broken links.
+ *
+ * @return a list of all broken links.
+ */
+ std::vector<Glib::ustring> findBrokenLinks(SPDocument *doc);
+
+ /**
+ * Resolve broken links as a whole and return a map for those that can be found.
+ *
+ * Note: this will allow for future enhancements including relinking to new locations
+ * with the most broken files found, etc.
+ *
+ * @return a map of found links.
+ */
+ std::map<Glib::ustring, Glib::ustring> locateLinks(Glib::ustring const & docbase, std::vector<Glib::ustring> const & brokenLinks);
+
+
+ /**
+ * Try to parse href into a local filename using standard methods.
+ *
+ * @return true if successful.
+ */
+ bool extractFilepath( Glib::ustring const &href, std::string &uri );
+
+ /**
+ * Try to parse href into a local filename using some non-standard methods.
+ * This means the href is likely invalid and should be rewritten.
+ *
+ * @return true if successful.
+ */
+ bool reconstructFilepath( Glib::ustring const &href, std::string &uri );
+
+ bool searchUpwards( std::string const &base, std::string const &subpath, std::string &dest );
+
+protected:
+};
+
+
+ResourceManagerImpl::ResourceManagerImpl()
+ : ResourceManager()
+{
+}
+
+ResourceManagerImpl::~ResourceManagerImpl()
+= default;
+
+bool ResourceManagerImpl::extractFilepath( Glib::ustring const &href, std::string &uri )
+{
+ bool isFile = false;
+
+ uri.clear();
+
+ std::string scheme = Glib::uri_parse_scheme(href);
+ if ( !scheme.empty() ) {
+ // TODO debug g_message("Scheme is now [%s]", scheme.c_str());
+ if ( scheme == "file" ) {
+ // TODO debug g_message("--- is a file URI [%s]", href.c_str());
+
+ // throws Glib::ConvertError:
+ try {
+ uri = Glib::filename_from_uri(href);
+ // TODO debug g_message(" [%s]", uri.c_str());
+ isFile = true;
+ } catch(Glib::ConvertError e) {
+ g_warning("%s", e.what().c_str());
+ }
+ }
+ } else {
+ // No scheme. Assuming it is a file path (absolute or relative).
+ // throws Glib::ConvertError:
+ uri = Glib::filename_from_utf8( href );
+ isFile = true;
+ }
+
+ return isFile;
+}
+
+bool ResourceManagerImpl::reconstructFilepath( Glib::ustring const &href, std::string &uri )
+{
+ bool isFile = false;
+
+ uri.clear();
+
+ std::string scheme = Glib::uri_parse_scheme(href);
+ if ( !scheme.empty() ) {
+ if ( scheme == "file" ) {
+ // try to build a relative filename for URIs like "file:image.png"
+ // they're not standard conformant but not uncommon
+ Glib::ustring href_new = Glib::ustring(href, 5);
+ uri = Glib::filename_from_utf8(href_new);
+ // TODO debug g_message("reconstructed path for '%s' into '%s'", href.c_str(), uri.c_str());
+ isFile = true;
+ }
+ }
+ return isFile;
+}
+
+
+std::vector<Glib::ustring> ResourceManagerImpl::findBrokenLinks( SPDocument *doc )
+{
+ std::vector<Glib::ustring> result;
+ std::set<Glib::ustring> uniques;
+
+ if ( doc ) {
+ std::vector<SPObject *> images = doc->getResourceList("image");
+ for (auto image : images) {
+ Inkscape::XML::Node *ir = image->getRepr();
+
+ gchar const *href = ir->attribute("xlink:href");
+ if ( href && ( uniques.find(href) == uniques.end() ) ) {
+ std::string uri;
+ if ( extractFilepath( href, uri ) ) {
+ if ( Glib::path_is_absolute(uri) ) {
+ if ( !Glib::file_test(uri, Glib::FILE_TEST_EXISTS) ) {
+ result.emplace_back(href);
+ uniques.insert(href);
+ }
+ } else {
+ std::string combined = Glib::build_filename(doc->getDocumentBase(), uri);
+ if ( !Glib::file_test(combined, Glib::FILE_TEST_EXISTS) ) {
+ result.emplace_back(href);
+ uniques.insert(href);
+ }
+ }
+ } else if ( reconstructFilepath( href, uri ) ) {
+ result.emplace_back(href);
+ uniques.insert(href);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+
+std::map<Glib::ustring, Glib::ustring> ResourceManagerImpl::locateLinks(Glib::ustring const & docbase, std::vector<Glib::ustring> const & brokenLinks)
+{
+ std::map<Glib::ustring, Glib::ustring> result;
+
+
+ // Note: we use a vector because we want them to stay in order:
+ std::vector<std::string> priorLocations;
+
+ Glib::RefPtr<Gtk::RecentManager> recentMgr = Gtk::RecentManager::get_default();
+ std::vector< Glib::RefPtr<Gtk::RecentInfo> > recentItems = recentMgr->get_items();
+ for (auto & recentItem : recentItems) {
+ Glib::ustring uri = recentItem->get_uri();
+ std::string scheme = Glib::uri_parse_scheme(uri);
+ if ( scheme == "file" ) {
+ try {
+ std::string path = Glib::filename_from_uri(uri);
+ path = Glib::path_get_dirname(path);
+ if ( std::find(priorLocations.begin(), priorLocations.end(), path) == priorLocations.end() ) {
+ // TODO debug g_message(" ==>[%s]", path.c_str());
+ priorLocations.push_back(path);
+ }
+ } catch (Glib::ConvertError e) {
+ g_warning("%s", e.what().c_str());
+ }
+ }
+ }
+
+ // At the moment we expect this list to contain file:// references, or simple relative or absolute paths.
+ for (const auto & brokenLink : brokenLinks) {
+ // TODO debug g_message("========{%s}", it->c_str());
+
+ std::string uri;
+ if ( extractFilepath( brokenLink, uri ) || reconstructFilepath( brokenLink, uri ) ) {
+ // We were able to get some path. Check it
+ std::string origPath = uri;
+
+ if ( !Glib::path_is_absolute(uri) ) {
+ uri = Glib::build_filename(docbase, uri);
+ // TODO debug g_message(" not absolute. Fixing up as [%s]", uri.c_str());
+ }
+
+ bool exists = Glib::file_test(uri, Glib::FILE_TEST_EXISTS);
+
+ // search in parent folders
+ if (!exists) {
+ exists = searchUpwards( docbase, origPath, uri );
+ }
+
+ // Check if the MRU bases point us to it.
+ if ( !exists ) {
+ if ( !Glib::path_is_absolute(origPath) ) {
+ for ( std::vector<std::string>::iterator it = priorLocations.begin(); !exists && (it != priorLocations.end()); ++it ) {
+ exists = searchUpwards( *it, origPath, uri );
+ }
+ }
+ }
+
+ if ( exists ) {
+ if ( Glib::path_is_absolute( uri ) ) {
+ // TODO debug g_message("Need to convert to relative if possible [%s]", uri.c_str());
+ uri = convertPathToRelative( uri, docbase );
+ }
+
+ bool isAbsolute = Glib::path_is_absolute( uri );
+ Glib::ustring replacement = isAbsolute ? Glib::filename_to_uri( uri ) : Glib::filename_to_utf8( uri );
+ result[brokenLink] = replacement;
+ }
+ }
+ }
+
+ return result;
+}
+
+bool ResourceManagerImpl::fixupBrokenLinks(SPDocument *doc)
+{
+ bool changed = false;
+ if ( doc ) {
+ // TODO debug g_message("FIXUP FIXUP FIXUP FIXUP FIXUP FIXUP FIXUP FIXUP FIXUP FIXUP");
+ // TODO debug g_message(" base is [%s]", doc->getDocumentBase());
+
+ std::vector<Glib::ustring> brokenHrefs = findBrokenLinks(doc);
+ if ( !brokenHrefs.empty() ) {
+ // TODO debug g_message(" FOUND SOME LINKS %d", static_cast<int>(brokenHrefs.size()));
+ for ( std::vector<Glib::ustring>::iterator it = brokenHrefs.begin(); it != brokenHrefs.end(); ++it ) {
+ // TODO debug g_message(" [%s]", it->c_str());
+ }
+ }
+
+ Glib::ustring base;
+ if (doc->getDocumentBase()) {
+ base = doc->getDocumentBase();
+ }
+
+ std::map<Glib::ustring, Glib::ustring> mapping = locateLinks(base, brokenHrefs);
+ for ( std::map<Glib::ustring, Glib::ustring>::iterator it = mapping.begin(); it != mapping.end(); ++it )
+ {
+ // TODO debug g_message(" [%s] ==> {%s}", it->first.c_str(), it->second.c_str());
+ }
+
+ bool savedUndoState = DocumentUndo::getUndoSensitive(doc);
+ DocumentUndo::setUndoSensitive(doc, true);
+
+ std::vector<SPObject *> images = doc->getResourceList("image");
+ for (auto image : images) {
+ Inkscape::XML::Node *ir = image->getRepr();
+
+ gchar const *href = ir->attribute("xlink:href");
+ if ( href ) {
+ // TODO debug g_message(" consider [%s]", href);
+
+ if ( mapping.find(href) != mapping.end() ) {
+ // TODO debug g_message(" Found a replacement");
+
+ ir->setAttributeOrRemoveIfEmpty( "xlink:href", mapping[href] );
+ if ( ir->attribute( "sodipodi:absref" ) ) {
+ ir->removeAttribute("sodipodi:absref"); // Remove this attribute
+ }
+
+ SPObject *updated = doc->getObjectByRepr(ir);
+ if (updated) {
+ // force immediate update of dependent attributes
+ updated->updateRepr();
+ }
+
+ changed = true;
+ }
+ }
+ }
+ if ( changed ) {
+ DocumentUndo::done( doc, SP_VERB_DIALOG_XML_EDITOR, _("Fixup broken links") );
+ }
+ DocumentUndo::setUndoSensitive(doc, savedUndoState);
+ }
+
+ return changed;
+}
+
+
+bool ResourceManagerImpl::searchUpwards( std::string const &base, std::string const &subpath, std::string &dest )
+{
+ bool exists = false;
+ // TODO debug g_message("............");
+
+ std::vector<std::string> parts = splitPath(subpath);
+ std::vector<std::string> baseParts = splitPath(base);
+
+ while ( !exists && !baseParts.empty() ) {
+ std::vector<std::string> current;
+ current.insert(current.begin(), parts.begin(), parts.end());
+ // TODO debug g_message(" ---{%s}", Glib::build_filename( baseParts ).c_str());
+ while ( !exists && !current.empty() ) {
+ std::vector<std::string> combined;
+ combined.insert( combined.end(), baseParts.begin(), baseParts.end() );
+ combined.insert( combined.end(), current.begin(), current.end() );
+ std::string filepath = Glib::build_filename( combined );
+ exists = Glib::file_test(filepath, Glib::FILE_TEST_EXISTS);
+ // TODO debug g_message(" ...[%s] %s", filepath.c_str(), (exists ? "XXX" : ""));
+ if ( exists ) {
+ dest = filepath;
+ }
+ current.erase( current.begin() );
+ }
+ baseParts.pop_back();
+ }
+
+ return exists;
+}
+
+
+static ResourceManagerImpl* theInstance = nullptr;
+
+ResourceManager::ResourceManager()
+= default;
+
+ResourceManager::~ResourceManager() = default;
+
+ResourceManager& ResourceManager::getManager() {
+ if ( !theInstance ) {
+ theInstance = new ResourceManagerImpl();
+ }
+
+ return *theInstance;
+}
+
+
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/io/resource-manager.h b/src/io/resource-manager.h
new file mode 100644
index 0000000..7c1e242
--- /dev/null
+++ b/src/io/resource-manager.h
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::ResourceManager - Manages external resources such as image and css files.
+ *
+ * Copyright 2011 Jon A. Cruz <jon@joncruz.org>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_RESOURCE_MANAGER_H
+#define SEEN_INKSCAPE_RESOURCE_MANAGER_H
+
+class SPDocument;
+
+namespace Inkscape {
+
+class ResourceManager {
+
+public:
+ static ResourceManager& getManager();
+
+ virtual bool fixupBrokenLinks(SPDocument *doc) = 0;
+
+protected:
+ ResourceManager();
+ virtual ~ResourceManager();
+
+private:
+ ResourceManager(ResourceManager const &) = delete; // no copy
+ void operator=(ResourceManager const &) = delete; // no assign
+};
+
+
+
+} // namespace Inkscape
+
+#endif // SEEN_INKSCAPE_RESOURCE_MANAGER_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/resource.cpp b/src/io/resource.cpp
new file mode 100644
index 0000000..d7b9731
--- /dev/null
+++ b/src/io/resource.cpp
@@ -0,0 +1,477 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Inkscape::IO::Resource - simple resource API
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Martin Owens <doctormo@gmail.com>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef _WIN32
+#include <shlobj.h> // for SHGetSpecialFolderLocation
+#endif
+
+#include <glibmm/i18n.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/stringutils.h>
+#include <glibmm/fileutils.h>
+
+#include "path-prefix.h"
+#include "io/sys.h"
+#include "io/resource.h"
+
+using Inkscape::IO::file_test;
+
+namespace Inkscape {
+
+namespace IO {
+
+namespace Resource {
+
+#define INKSCAPE_PROFILE_DIR "inkscape"
+
+gchar *_get_path(Domain domain, Type type, char const *filename)
+{
+ gchar *path=nullptr;
+ switch (domain) {
+ case SYSTEM: {
+ gchar const* temp = nullptr;
+ switch (type) {
+ case EXTENSIONS: temp = INKSCAPE_EXTENSIONDIR; break;
+ case FILTERS: temp = INKSCAPE_FILTERDIR; break;
+ case FONTS: temp = INKSCAPE_FONTSDIR; break;
+ case ICONS: temp = INKSCAPE_ICONSDIR; break;
+ case KEYS: temp = INKSCAPE_KEYSDIR; break;
+ case MARKERS: temp = INKSCAPE_MARKERSDIR; break;
+ case NONE: g_assert_not_reached(); break;
+ case PAINT: temp = INKSCAPE_PAINTDIR; break;
+ case PALETTES: temp = INKSCAPE_PALETTESDIR; break;
+ case SCREENS: temp = INKSCAPE_SCREENSDIR; break;
+ case SYMBOLS: temp = INKSCAPE_SYMBOLSDIR; break;
+ case TEMPLATES: temp = INKSCAPE_TEMPLATESDIR; break;
+ case THEMES: temp = INKSCAPE_THEMEDIR; break;
+ case TUTORIALS: temp = INKSCAPE_TUTORIALSDIR; break;
+ case UIS: temp = INKSCAPE_UIDIR; break;
+ case PIXMAPS: temp = INKSCAPE_PIXMAPSDIR; break;
+ default: temp = "";
+ }
+ path = g_strdup(temp);
+ } break;
+ case CREATE: {
+ gchar const* temp = nullptr;
+ switch (type) {
+ case PAINT: temp = CREATE_PAINTDIR; break;
+ case PALETTES: temp = CREATE_PALETTESDIR; break;
+ default: temp = "";
+ }
+ path = g_strdup(temp);
+ } break;
+ case CACHE: {
+ path = g_build_filename(g_get_user_cache_dir(), "inkscape", NULL);
+ } break;
+ case USER: {
+ char const *name=nullptr;
+ switch (type) {
+ case EXTENSIONS: name = "extensions"; break;
+ case FILTERS: name = "filters"; break;
+ case FONTS: name = "fonts"; break;
+ case ICONS: name = "icons"; break;
+ case KEYS: name = "keys"; break;
+ case MARKERS: name = "markers"; break;
+ case NONE: name = ""; break;
+ case PAINT: name = "paint"; break;
+ case PALETTES: name = "palettes"; break;
+ case SYMBOLS: name = "symbols"; break;
+ case TEMPLATES: name = "templates"; break;
+ case THEMES: name = "themes"; break;
+ case UIS: name = "ui"; break;
+ case PIXMAPS: name = "pixmaps"; break;
+ default: return _get_path(SYSTEM, type, filename);
+ }
+ path = profile_path(name);
+ } break;
+ }
+
+
+ if (filename && path) {
+ gchar *temp=g_build_filename(path, filename, NULL);
+ g_free(path);
+ path = temp;
+ }
+
+ return path;
+}
+
+
+
+Util::ptr_shared get_path(Domain domain, Type type, char const *filename)
+{
+ char *path = _get_path(domain, type, filename);
+ Util::ptr_shared result=Util::share_string(path);
+ g_free(path);
+ return result;
+}
+Glib::ustring get_path_ustring(Domain domain, Type type, char const *filename)
+{
+ Glib::ustring result;
+ char *path = _get_path(domain, type, filename);
+ if(path) {
+ result = Glib::ustring(path);
+ g_free(path);
+ }
+ return result;
+}
+
+/*
+ * Same as get_path, but checks for file's existence and falls back
+ * from USER to SYSTEM modes.
+ *
+ * type - The type of file to get, such as extension, template, ui etc
+ * filename - The filename to get, i.e. preferences.xml
+ * localized - Prefer a localized version of the file, i.e. default.de.svg instead of default.svg.
+ * (will use gettext to determine the preferred language of the user)
+ * silent - do not warn if file doesnt exist
+ *
+ */
+Glib::ustring get_filename(Type type, char const *filename, bool localized, bool silent)
+{
+ Glib::ustring result;
+
+ char *user_filename = nullptr;
+ char *sys_filename = nullptr;
+ char *user_filename_localized = nullptr;
+ char *sys_filename_localized = nullptr;
+
+ // TRANSLATORS: 'en' is an ISO 639-1 language code.
+ // Replace with language code for your language, i.e. the name of your .po file
+ localized = localized && strcmp(_("en"), "en");
+
+ if (localized) {
+ Glib::ustring localized_filename = filename;
+ localized_filename.insert(localized_filename.rfind('.'), ".");
+ localized_filename.insert(localized_filename.rfind('.'), _("en"));
+
+ user_filename_localized = _get_path(USER, type, localized_filename.c_str());
+ sys_filename_localized = _get_path(SYSTEM, type, localized_filename.c_str());
+ }
+ user_filename = _get_path(USER, type, filename);
+ sys_filename = _get_path(SYSTEM, type, filename);
+
+ // impose the following load order:
+ // USER (localized) > USER > SYSTEM (localized) > SYSTEM
+ if (localized && file_test(user_filename_localized, G_FILE_TEST_EXISTS)) {
+ result = Glib::ustring(user_filename_localized);
+ g_info("Found localized version of resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
+ } else if (file_test(user_filename, G_FILE_TEST_EXISTS)) {
+ result = Glib::ustring(user_filename);
+ g_info("Found resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
+ } else if (localized && file_test(sys_filename_localized, G_FILE_TEST_EXISTS)) {
+ result = Glib::ustring(sys_filename_localized);
+ g_info("Found localized version of resource file '%s' in system directory:\n\t%s", filename, result.c_str());
+ } else if (file_test(sys_filename, G_FILE_TEST_EXISTS)) {
+ result = Glib::ustring(sys_filename);
+ g_info("Found resource file '%s' in system directory:\n\t%s", filename, result.c_str());
+ } else if (!silent) {
+ if (localized) {
+ g_warning("Failed to find resource file '%s'. Looked in:\n\t%s\n\t%s\n\t%s\n\t%s",
+ filename, user_filename_localized, user_filename, sys_filename_localized, sys_filename);
+ } else {
+ g_warning("Failed to find resource file '%s'. Looked in:\n\t%s\n\t%s",
+ filename, user_filename, sys_filename);
+ }
+ }
+
+ g_free(user_filename);
+ g_free(sys_filename);
+ g_free(user_filename_localized);
+ g_free(sys_filename_localized);
+
+ return result;
+}
+
+/*
+ * Similar to get_filename, but takes a path (or filename) for relative resolution
+ *
+ * path - A directory or filename that is considered local to the path resolution.
+ * filename - The filename that we are looking for.
+ */
+Glib::ustring get_filename(Glib::ustring path, Glib::ustring filename)
+{
+ // Test if it's a filename and get the parent directory instead
+ if (Glib::file_test(path, Glib::FILE_TEST_IS_REGULAR)) {
+ return get_filename(g_path_get_dirname(path.c_str()), filename);
+ }
+ if (g_path_is_absolute(filename.c_str())) {
+ if (Glib::file_test(filename, Glib::FILE_TEST_EXISTS)) {
+ return filename;
+ }
+ } else {
+ Glib::ustring ret = Glib::build_filename(path, filename);
+ if (Glib::file_test(ret, Glib::FILE_TEST_EXISTS)) {
+ return ret;
+ }
+ }
+ return Glib::ustring();
+}
+
+/*
+ * Gets all the files in a given type, for all domain types.
+ *
+ * domain - Optional domain (overload), will check return domains if not.
+ * type - The type of files, e.g. TEMPLATES
+ * extensions - A list of extensions to return, e.g. xml, svg
+ * exclusions - A list of names to exclude e.g. default.xml
+ */
+std::vector<Glib::ustring> get_filenames(Type type, std::vector<const char *> extensions, std::vector<const char *> exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_filenames_from_path(ret, get_path_ustring(USER, type), extensions, exclusions);
+ get_filenames_from_path(ret, get_path_ustring(SYSTEM, type), extensions, exclusions);
+ get_filenames_from_path(ret, get_path_ustring(CREATE, type), extensions, exclusions);
+ return ret;
+}
+
+std::vector<Glib::ustring> get_filenames(Domain domain, Type type, std::vector<const char *> extensions, std::vector<const char *> exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_filenames_from_path(ret, get_path_ustring(domain, type), extensions, exclusions);
+ return ret;
+}
+std::vector<Glib::ustring> get_filenames(Glib::ustring path, std::vector<const char *> extensions, std::vector<const char *> exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_filenames_from_path(ret, path, extensions, exclusions);
+ return ret;
+}
+
+/*
+ * Gets all folders inside each type, for all domain types.
+ *
+ * domain - Optional domain (overload), will check return domains if not.
+ * type - The type of files, e.g. TEMPLATES
+ * extensions - A list of extensions to return, e.g. xml, svg
+ * exclusions - A list of names to exclude e.g. default.xml
+ */
+std::vector<Glib::ustring> get_foldernames(Type type, std::vector<const char *> exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_foldernames_from_path(ret, get_path_ustring(USER, type), exclusions);
+ get_foldernames_from_path(ret, get_path_ustring(SYSTEM, type), exclusions);
+ get_foldernames_from_path(ret, get_path_ustring(CREATE, type), exclusions);
+ return ret;
+}
+
+std::vector<Glib::ustring> get_foldernames(Domain domain, Type type, std::vector<const char *> exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_foldernames_from_path(ret, get_path_ustring(domain, type), exclusions);
+ return ret;
+}
+std::vector<Glib::ustring> get_foldernames(Glib::ustring path, std::vector<const char *> exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_foldernames_from_path(ret, path, exclusions);
+ return ret;
+}
+
+
+/*
+ * Get all the files from a specific path and any sub-dirs, populating &files vector
+ *
+ * &files - Output list to populate, will be populated with full paths
+ * path - The directory to parse, will add nothing if directory doesn't exist
+ * extensions - Only add files with these extensions, they must be duplicated
+ * exclusions - Exclude files that exactly match these names.
+ */
+void get_filenames_from_path(std::vector<Glib::ustring> &files, Glib::ustring path, std::vector<const char *> extensions, std::vector<const char *> exclusions)
+{
+ if(!Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
+ return;
+ }
+
+ Glib::Dir dir(path);
+ std::string file = dir.read_name();
+ while (!file.empty()){
+ // If not extensions are specified, don't reject ANY files.
+ bool reject = !extensions.empty();
+
+ // Unreject any file which has one of the extensions.
+ for (auto &ext: extensions) {
+ reject ^= Glib::str_has_suffix(file, ext);
+ }
+
+ // Reject any file which matches the exclusions.
+ for (auto &exc: exclusions) {
+ reject |= Glib::str_has_prefix(file, exc);
+ }
+
+ // Reject any filename which isn't a regular file
+ Glib::ustring filename = Glib::build_filename(path, file);
+
+ if(Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) {
+ get_filenames_from_path(files, filename, extensions, exclusions);
+ } else if(Glib::file_test(filename, Glib::FILE_TEST_IS_REGULAR) && !reject) {
+ files.push_back(filename);
+ }
+ file = dir.read_name();
+ }
+}
+
+/*
+ * Get all the files from a specific path and any sub-dirs, populating &files vector
+ *
+ * &folders - Output list to populate, will be poulated with full paths
+ * path - The directory to parse, will add nothing if directory doesn't exist
+ * exclusions - Exclude files that exactly match these names.
+ */
+void get_foldernames_from_path(std::vector<Glib::ustring> &folders, Glib::ustring path,
+ std::vector<const char *> exclusions)
+{
+ if (!Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
+ return;
+ }
+
+ Glib::Dir dir(path);
+ std::string file = dir.read_name();
+ while (!file.empty()) {
+ // If not extensions are specified, don't reject ANY files.
+ bool reject = false;
+
+ // Reject any file which matches the exclusions.
+ for (auto &exc : exclusions) {
+ reject |= Glib::str_has_prefix(file, exc);
+ }
+
+ // Reject any filename which isn't a regular file
+ Glib::ustring filename = Glib::build_filename(path, file);
+
+ if (Glib::file_test(filename, Glib::FILE_TEST_IS_DIR) && !reject) {
+ folders.push_back(filename);
+ }
+ file = dir.read_name();
+ }
+}
+
+
+/**
+ * Get, or guess, or decide the location where the preferences.xml
+ * file should be located. This also indicates where all other inkscape
+ * shared files may optionally exist.
+ */
+char *profile_path(const char *filename)
+{
+ static const gchar *prefdir = nullptr;
+
+ if (!prefdir) {
+ // Check if profile directory is overridden using environment variable
+ gchar const *userenv = g_getenv("INKSCAPE_PROFILE_DIR");
+ if (userenv) {
+ prefdir = g_strdup(userenv);
+ }
+
+#ifdef _WIN32
+ // prefer c:\Documents and Settings\UserName\Application Data\ to c:\Documents and Settings\userName\;
+ // TODO: CSIDL_APPDATA is C:\Users\UserName\AppData\Roaming these days
+ // should we migrate to AppData\Local? Then we can simply use the portable g_get_user_config_dir()
+ if (!prefdir) {
+ ITEMIDLIST *pidl = 0;
+ if ( SHGetFolderLocation( NULL, CSIDL_APPDATA, NULL, 0, &pidl ) == S_OK ) {
+ gchar * utf8Path = NULL;
+
+ {
+ wchar_t pathBuf[MAX_PATH+1];
+ g_assert(sizeof(wchar_t) == sizeof(gunichar2));
+
+ if ( SHGetPathFromIDListW( pidl, pathBuf ) ) {
+ utf8Path = g_utf16_to_utf8( (gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL );
+ }
+ }
+
+ if ( utf8Path ) {
+ if (!g_utf8_validate(utf8Path, -1, NULL)) {
+ g_warning( "SHGetPathFromIDListW() resulted in invalid UTF-8");
+ g_free( utf8Path );
+ utf8Path = 0;
+ } else {
+ prefdir = utf8Path;
+ }
+ }
+ }
+
+ if (prefdir) {
+ const char *prefdir_profile = g_build_filename(prefdir, INKSCAPE_PROFILE_DIR, NULL);
+ g_free((void *)prefdir);
+ prefdir = prefdir_profile;
+ }
+ }
+#endif
+ if (!prefdir) {
+ prefdir = g_build_filename(g_get_user_config_dir(), INKSCAPE_PROFILE_DIR, NULL);
+ // In case the XDG user config dir of the moment does not yet exist...
+ int mode = S_IRWXU;
+#ifdef S_IRGRP
+ mode |= S_IRGRP;
+#endif
+#ifdef S_IXGRP
+ mode |= S_IXGRP;
+#endif
+#ifdef S_IXOTH
+ mode |= S_IXOTH;
+#endif
+ if ( g_mkdir_with_parents(prefdir, mode) == -1 ) {
+ int problem = errno;
+ g_warning("Unable to create profile directory (%s) (%d)", g_strerror(problem), problem);
+ } else {
+ gchar const *userDirs[] = { "keys", "templates", "icons", "extensions", "ui",
+ "symbols", "paint", "themes", "palettes", nullptr };
+ for (gchar const** name = userDirs; *name; ++name) {
+ gchar *dir = g_build_filename(prefdir, *name, NULL);
+ g_mkdir_with_parents(dir, mode);
+ g_free(dir);
+ }
+ }
+ }
+ }
+ return g_build_filename(prefdir, filename, NULL);
+}
+
+/*
+ * We return the profile_path because that is where most documentation
+ * days log files will be generated in inkscape 0.92
+ */
+char *log_path(const char *filename)
+{
+ return profile_path(filename);
+}
+
+char *homedir_path(const char *filename)
+{
+ static const gchar *homedir = nullptr;
+ homedir = g_get_home_dir();
+
+ return g_build_filename(homedir, filename, NULL);
+}
+
+}
+
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/resource.h b/src/io/resource.h
new file mode 100644
index 0000000..fc1f066
--- /dev/null
+++ b/src/io/resource.h
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Martin Owens <doctormo@gmail.com>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_IO_RESOURCE_H
+#define SEEN_INKSCAPE_IO_RESOURCE_H
+
+#include <vector>
+#include <glibmm/ustring.h>
+#include "util/share.h"
+
+namespace Inkscape {
+
+namespace IO {
+
+/**
+ * simple resource API
+ */
+namespace Resource {
+
+enum Type {
+ EXTENSIONS,
+ FONTS,
+ ICONS,
+ KEYS,
+ MARKERS,
+ NONE,
+ PAINT,
+ PALETTES,
+ SCREENS,
+ TEMPLATES,
+ TUTORIALS,
+ SYMBOLS,
+ FILTERS,
+ THEMES,
+ UIS,
+ PIXMAPS,
+};
+
+enum Domain {
+ SYSTEM,
+ CREATE,
+ CACHE,
+ USER
+};
+
+Util::ptr_shared get_path(Domain domain, Type type,
+ char const *filename=nullptr);
+
+Glib::ustring get_path_ustring(Domain domain, Type type,
+ char const *filename=nullptr);
+
+Glib::ustring get_filename(Type type, char const *filename, bool localized = false, bool silent = false);
+Glib::ustring get_filename(Glib::ustring path, Glib::ustring filename);
+
+std::vector<Glib::ustring> get_filenames(Type type,
+ std::vector<const char *> extensions={},
+ std::vector<const char *> exclusions={});
+
+std::vector<Glib::ustring> get_filenames(Domain domain, Type type,
+ std::vector<const char *> extensions={},
+ std::vector<const char *> exclusions={});
+
+std::vector<Glib::ustring> get_filenames(Glib::ustring path,
+ std::vector<const char *> extensions={},
+ std::vector<const char *> exclusions={});
+
+std::vector<Glib::ustring> get_foldernames(Type type, std::vector<const char *> exclusions = {});
+
+std::vector<Glib::ustring> get_foldernames(Domain domain, Type type, std::vector<const char *> exclusions = {});
+
+std::vector<Glib::ustring> get_foldernames(Glib::ustring path, std::vector<const char *> exclusions = {});
+
+void get_filenames_from_path(std::vector<Glib::ustring> &files,
+ Glib::ustring path,
+ std::vector<const char *> extensions={},
+ std::vector<const char *> exclusions={});
+
+void get_foldernames_from_path(std::vector<Glib::ustring> &files, Glib::ustring path,
+ std::vector<const char *> exclusions = {});
+
+
+char *profile_path(const char *filename);
+char *homedir_path(const char *filename);
+char *log_path(const char *filename);
+
+}
+
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/stream/Makefile.tst b/src/io/stream/Makefile.tst
new file mode 100644
index 0000000..2e3142d
--- /dev/null
+++ b/src/io/stream/Makefile.tst
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+##############################################
+#
+# Test makefile for InkscapeStreams
+#
+##############################################
+
+
+CC = gcc
+CXX = g++
+
+
+INC = -I. -I..
+
+XSLT_CFLAGS = `pkg-config --cflags libxslt`
+XSLT_LIBS = `pkg-config --libs libxslt`
+
+GLIBMM_CFLAGS = `pkg-config --cflags glibmm-2.4`
+GLIBMM_LIBS = `pkg-config --libs glibmm-2.4`
+
+CFLAGS = -g $(GLIBMM_CFLAGS) $(XSLT_CFLAGS)
+LIBS = $(GLIBMM_LIBS) $(XSLT_LIBS) ../uri.o -lz
+
+OBJ = \
+inkscapestream.o \
+base64stream.o \
+gzipstream.o \
+stringstream.o \
+uristream.o \
+xsltstream.o \
+ftos.o
+
+all: streamtest
+
+streamtest: inkscapestream.h libstream.a streamtest.o
+ $(CXX) -o streamtest streamtest.o libstream.a $(LIBS)
+
+libstream.a: $(OBJ)
+ ar crv libstream.a $(OBJ)
+
+
+.cpp.o:
+ $(CXX) $(CFLAGS) $(INC) -c -o $@ $<
+
+clean:
+ -$(RM) *.o *.a
+ -$(RM) streamtest
+
diff --git a/src/io/stream/README b/src/io/stream/README
new file mode 100644
index 0000000..67d8721
--- /dev/null
+++ b/src/io/stream/README
@@ -0,0 +1,13 @@
+
+This directory contains code related to streams.
+
+Base class:
+ inkscapestream.h Used by other stream handling code and odf, filter-file
+
+Derived classes:
+ bufferstream.h Used by odf
+ gzipstream.h Used by repr-io.cpp
+ stringstream.h Used by rerp-io.cpp and ui/clipboard.cpp
+ Note file with same name in src/svg
+ uristream.h Used by repr-io.cpp
+ xsltstream.h Used by none one (except testfile)
diff --git a/src/io/stream/bufferstream.cpp b/src/io/stream/bufferstream.cpp
new file mode 100644
index 0000000..1723ee1
--- /dev/null
+++ b/src/io/stream/bufferstream.cpp
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * @file
+ * Phoebe DOM Implementation.
+ *
+ * This is a C++ approximation of the W3C DOM model, which follows
+ * fairly closely the specifications in the various .idl files, copies of
+ * which are provided for reference. Most important is this one:
+ *
+ * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
+ *//*
+ * Authors:
+ * see git history
+ * Bob Jamison
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
+ */
+
+/**
+ * This class provided buffered endpoints for input and output.
+ */
+
+#include "bufferstream.h"
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# B U F F E R I N P U T S T R E A M
+//#########################################################################
+/**
+ *
+ */
+BufferInputStream::BufferInputStream(
+ const std::vector<unsigned char> &sourceBuffer)
+ : buffer(sourceBuffer)
+{
+ position = 0;
+ closed = false;
+}
+
+/**
+ *
+ */
+BufferInputStream::~BufferInputStream()
+= default;
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int BufferInputStream::available()
+{
+ if (closed)
+ return -1;
+ return buffer.size() - position;
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void BufferInputStream::close()
+{
+ closed = true;
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int BufferInputStream::get()
+{
+ if (closed)
+ return -1;
+ if (position >= (int)buffer.size())
+ return -1;
+ int ch = (int) buffer[position++];
+ return ch;
+}
+
+
+
+
+//#########################################################################
+//# B U F F E R O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ *
+ */
+BufferOutputStream::BufferOutputStream()
+{
+ closed = false;
+}
+
+/**
+ *
+ */
+BufferOutputStream::~BufferOutputStream()
+= default;
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void BufferOutputStream::close()
+{
+ closed = true;
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void BufferOutputStream::flush()
+{
+ //nothing to do
+}
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int BufferOutputStream::put(char ch)
+{
+ if (closed)
+ return -1;
+ buffer.push_back(ch);
+ return 1;
+}
+
+
+
+
+} //namespace IO
+} //namespace Inkscape
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
diff --git a/src/io/stream/bufferstream.h b/src/io/stream/bufferstream.h
new file mode 100644
index 0000000..811ab0d
--- /dev/null
+++ b/src/io/stream/bufferstream.h
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * @file
+ * Phoebe DOM Implementation.
+ *
+ * This is a C++ approximation of the W3C DOM model, which follows
+ * fairly closely the specifications in the various .idl files, copies of
+ * which are provided for reference. Most important is this one:
+ *
+ * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
+ *//*
+ * Authors:
+ * see git history
+ * Bob Jamison
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_BUFFERSTREAM_H
+#define SEEN_BUFFERSTREAM_H
+
+
+#include <vector>
+#include "inkscapestream.h"
+
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# S T R I N G I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for reading character from a DOMString
+ *
+ */
+class BufferInputStream : public InputStream
+{
+
+public:
+
+ BufferInputStream(const std::vector<unsigned char> &sourceBuffer);
+ ~BufferInputStream() override;
+ int available() override;
+ void close() override;
+ int get() override;
+
+private:
+ const std::vector<unsigned char> &buffer;
+ long position;
+ bool closed;
+
+}; // class BufferInputStream
+
+
+
+
+//#########################################################################
+//# B U F F E R O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for sending a stream to a character buffer
+ *
+ */
+class BufferOutputStream : public OutputStream
+{
+
+public:
+
+ BufferOutputStream();
+ ~BufferOutputStream() override;
+ void close() override;
+ void flush() override;
+ int put(char ch) override;
+ virtual std::vector<unsigned char> &getBuffer()
+ { return buffer; }
+
+ virtual void clear()
+ { buffer.clear(); }
+
+private:
+ std::vector<unsigned char> buffer;
+ bool closed;
+
+}; // class BufferOutputStream
+
+
+
+} //namespace IO
+} //namespace Inkscape
+
+
+
+#endif // SEEN_BUFFERSTREAM_H
diff --git a/src/io/stream/gzipstream.cpp b/src/io/stream/gzipstream.cpp
new file mode 100644
index 0000000..03960b0
--- /dev/null
+++ b/src/io/stream/gzipstream.cpp
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/** @file
+ * Zlib-enabled input and output streams
+ *//*
+ * Authors:
+ * see git history
+ * Bob Jamison <rjamison@titan.com>
+ *
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
+ */
+/*
+ * This is a thin wrapper of libz calls, in order
+ * to provide a simple interface to our developers
+ * for gzip input and output.
+ */
+
+#include "gzipstream.h"
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# G Z I P I N P U T S T R E A M
+//#########################################################################
+
+#define OUT_SIZE 4000
+
+/**
+ *
+ */
+GzipInputStream::GzipInputStream(InputStream &sourceStream)
+ : BasicInputStream(sourceStream),
+ loaded(false),
+ totalIn(0),
+ totalOut(0),
+ outputBuf(nullptr),
+ srcBuf(nullptr),
+ crc(0),
+ srcCrc(0),
+ srcSiz(0),
+ srcConsumed(0),
+ srcLen(0),
+ outputBufPos(0),
+ outputBufLen(0)
+{
+ memset( &d_stream, 0, sizeof(d_stream) );
+}
+
+/**
+ *
+ */
+GzipInputStream::~GzipInputStream()
+{
+ close();
+ if ( srcBuf ) {
+ delete[] srcBuf;
+ srcBuf = nullptr;
+ }
+ if ( outputBuf ) {
+ delete[] outputBuf;
+ outputBuf = nullptr;
+ }
+}
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int GzipInputStream::available()
+{
+ if (closed || !outputBuf)
+ return 0;
+ return outputBufLen - outputBufPos;
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void GzipInputStream::close()
+{
+ if (closed)
+ return;
+
+ int zerr = inflateEnd(&d_stream);
+ if (zerr != Z_OK) {
+ printf("inflateEnd: Some kind of problem: %d\n", zerr);
+ }
+
+ if ( srcBuf ) {
+ delete[] srcBuf;
+ srcBuf = nullptr;
+ }
+ if ( outputBuf ) {
+ delete[] outputBuf;
+ outputBuf = nullptr;
+ }
+ closed = true;
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int GzipInputStream::get()
+{
+ int ch = -1;
+ if (closed) {
+ // leave return value -1
+ }
+ else if (!loaded && !load()) {
+ closed=true;
+ } else {
+ loaded = true;
+
+ if ( outputBufPos >= outputBufLen ) {
+ // time to read more, if we can
+ fetchMore();
+ }
+
+ if ( outputBufPos < outputBufLen ) {
+ ch = (int)outputBuf[outputBufPos++];
+ }
+ }
+
+ return ch;
+}
+
+#define FTEXT 0x01
+#define FHCRC 0x02
+#define FEXTRA 0x04
+#define FNAME 0x08
+#define FCOMMENT 0x10
+
+bool GzipInputStream::load()
+{
+ crc = crc32(0L, Z_NULL, 0);
+
+ std::vector<Byte> inputBuf;
+ while (true)
+ {
+ int ch = source.get();
+ if (ch<0)
+ break;
+ inputBuf.push_back(static_cast<Byte>(ch & 0xff));
+ }
+ long inputBufLen = inputBuf.size();
+
+ if (inputBufLen < 19) //header + tail + 1
+ {
+ return false;
+ }
+
+ srcLen = inputBuf.size();
+ srcBuf = new (std::nothrow) Byte [srcLen];
+ if (!srcBuf) {
+ return false;
+ }
+
+ outputBuf = new (std::nothrow) unsigned char [OUT_SIZE];
+ if ( !outputBuf ) {
+ delete[] srcBuf;
+ srcBuf = nullptr;
+ return false;
+ }
+ outputBufLen = 0; // Not filled in yet
+
+ std::vector<unsigned char>::iterator iter;
+ Bytef *p = srcBuf;
+ for (iter=inputBuf.begin() ; iter != inputBuf.end() ; ++iter)
+ {
+ *p++ = *iter;
+ }
+
+ int headerLen = 10;
+
+ //Magic
+ //int val = (int)srcBuf[0];
+ ////printf("val:%x\n", val);
+ //val = (int)srcBuf[1];
+ ////printf("val:%x\n", val);
+
+ ////Method
+ //val = (int)srcBuf[2];
+ ////printf("val:%x\n", val);
+
+ //flags
+ int flags = static_cast<int>(srcBuf[3]);
+
+ ////time
+ //val = (int)srcBuf[4];
+ //val = (int)srcBuf[5];
+ //val = (int)srcBuf[6];
+ //val = (int)srcBuf[7];
+
+ ////xflags
+ //val = (int)srcBuf[8];
+ ////OS
+ //val = (int)srcBuf[9];
+
+// if ( flags & FEXTRA ) {
+// headerLen += 2;
+// int xlen =
+// TODO deal with optional header parts
+// }
+ if ( flags & FNAME ) {
+ int cur = 10;
+ while ( srcBuf[cur] )
+ {
+ cur++;
+ headerLen++;
+ }
+ headerLen++;
+ }
+
+
+ srcCrc = ((0x0ff & srcBuf[srcLen - 5]) << 24)
+ | ((0x0ff & srcBuf[srcLen - 6]) << 16)
+ | ((0x0ff & srcBuf[srcLen - 7]) << 8)
+ | ((0x0ff & srcBuf[srcLen - 8]) << 0);
+ //printf("srcCrc:%lx\n", srcCrc);
+
+ srcSiz = ((0x0ff & srcBuf[srcLen - 1]) << 24)
+ | ((0x0ff & srcBuf[srcLen - 2]) << 16)
+ | ((0x0ff & srcBuf[srcLen - 3]) << 8)
+ | ((0x0ff & srcBuf[srcLen - 4]) << 0);
+ //printf("srcSiz:%lx/%ld\n", srcSiz, srcSiz);
+
+ //outputBufLen = srcSiz + srcSiz/100 + 14;
+
+ unsigned char *data = srcBuf + headerLen;
+ unsigned long dataLen = srcLen - (headerLen + 8);
+ //printf("%x %x\n", data[0], data[dataLen-1]);
+
+ d_stream.zalloc = (alloc_func)nullptr;
+ d_stream.zfree = (free_func)nullptr;
+ d_stream.opaque = (voidpf)nullptr;
+ d_stream.next_in = data;
+ d_stream.avail_in = dataLen;
+ d_stream.next_out = outputBuf;
+ d_stream.avail_out = OUT_SIZE;
+
+ int zerr = inflateInit2(&d_stream, -MAX_WBITS);
+ if ( zerr == Z_OK )
+ {
+ zerr = fetchMore();
+ } else {
+ printf("inflateInit2: Some kind of problem: %d\n", zerr);
+ }
+
+
+ return (zerr == Z_OK) || (zerr == Z_STREAM_END);
+}
+
+
+int GzipInputStream::fetchMore()
+{
+ // TODO assumes we aren't called till the buffer is empty
+ d_stream.next_out = outputBuf;
+ d_stream.avail_out = OUT_SIZE;
+ outputBufLen = 0;
+ outputBufPos = 0;
+
+ int zerr = inflate( &d_stream, Z_SYNC_FLUSH );
+ if ( zerr == Z_OK || zerr == Z_STREAM_END ) {
+ outputBufLen = OUT_SIZE - d_stream.avail_out;
+ if ( outputBufLen ) {
+ crc = crc32(crc, const_cast<const Bytef *>(outputBuf), outputBufLen);
+ }
+ //printf("crc:%lx\n", crc);
+// } else if ( zerr != Z_STREAM_END ) {
+// // TODO check to be sure this won't happen for partial end reads
+// printf("inflate: Some kind of problem: %d\n", zerr);
+ }
+
+ return zerr;
+}
+
+//#########################################################################
+//# G Z I P O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ *
+ */
+GzipOutputStream::GzipOutputStream(OutputStream &destinationStream)
+ : BasicOutputStream(destinationStream)
+{
+
+ totalIn = 0;
+ totalOut = 0;
+ crc = crc32(0L, Z_NULL, 0);
+
+ //Gzip header
+ destination.put(0x1f);
+ destination.put(0x8b);
+
+ //Say it is compressed
+ destination.put(Z_DEFLATED);
+
+ //flags
+ destination.put(0);
+
+ //time
+ destination.put(0);
+ destination.put(0);
+ destination.put(0);
+ destination.put(0);
+
+ //xflags
+ destination.put(0);
+
+ //OS code - from zutil.h
+ //destination.put(OS_CODE);
+ //apparently, we should not explicitly include zutil.h
+ destination.put(0);
+
+}
+
+/**
+ *
+ */
+GzipOutputStream::~GzipOutputStream()
+{
+ close();
+}
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void GzipOutputStream::close()
+{
+ if (closed)
+ return;
+
+ flush();
+
+ //# Send the CRC
+ uLong outlong = crc;
+ for (int n = 0; n < 4; n++)
+ {
+ destination.put(static_cast<char>(outlong & 0xff));
+ outlong >>= 8;
+ }
+ //# send the file length
+ outlong = totalIn & 0xffffffffL;
+ for (int n = 0; n < 4; n++)
+ {
+ destination.put(static_cast<char>(outlong & 0xff));
+ outlong >>= 8;
+ }
+
+ destination.close();
+ closed = true;
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void GzipOutputStream::flush()
+{
+ if (closed || inputBuf.empty())
+ {
+ return;
+ }
+
+ uLong srclen = inputBuf.size();
+ Bytef *srcbuf = new (std::nothrow) Bytef [srclen];
+ if (!srcbuf)
+ {
+ return;
+ }
+
+ uLong destlen = srclen;
+ Bytef *destbuf = new (std::nothrow) Bytef [(destlen + (srclen/100) + 13)];
+ if (!destbuf)
+ {
+ delete[] srcbuf;
+ return;
+ }
+
+ std::vector<unsigned char>::iterator iter;
+ Bytef *p = srcbuf;
+ for (iter=inputBuf.begin() ; iter != inputBuf.end() ; ++iter)
+ *p++ = *iter;
+
+ crc = crc32(crc, const_cast<const Bytef *>(srcbuf), srclen);
+
+ int zerr = compress(destbuf, static_cast<uLongf *>(&destlen), srcbuf, srclen);
+ if (zerr != Z_OK)
+ {
+ printf("Some kind of problem\n");
+ }
+
+ totalOut += destlen;
+ //skip the redundant zlib header and checksum
+ for (uLong i=2; i<destlen-4 ; i++)
+ {
+ destination.put((int)destbuf[i]);
+ }
+
+ destination.flush();
+
+ inputBuf.clear();
+ delete[] srcbuf;
+ delete[] destbuf;
+}
+
+
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int GzipOutputStream::put(char ch)
+{
+ if (closed)
+ {
+ //probably throw an exception here
+ return -1;
+ }
+
+
+ //Add char to buffer
+ inputBuf.push_back(ch);
+ totalIn++;
+ return 1;
+}
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/io/stream/gzipstream.h b/src/io/stream/gzipstream.h
new file mode 100644
index 0000000..fc80f97
--- /dev/null
+++ b/src/io/stream/gzipstream.h
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_IO_GZIPSTREAM_H
+#define SEEN_INKSCAPE_IO_GZIPSTREAM_H
+/**
+ * @file
+ * Zlib-enabled input and output streams.
+ *
+ * This is a thin wrapper of libz calls, in order
+ * to provide a simple interface to our developers
+ * for gzip input and output.
+ */
+/*
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <vector>
+#include "inkscapestream.h"
+#include <zlib.h>
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# G Z I P I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for deflating a gzip-compressed InputStream source
+ *
+ */
+class GzipInputStream : public BasicInputStream
+{
+
+public:
+
+ GzipInputStream(InputStream &sourceStream);
+
+ ~GzipInputStream() override;
+
+ int available() override;
+
+ void close() override;
+
+ int get() override;
+
+private:
+
+ bool load();
+ int fetchMore();
+
+ bool loaded;
+
+ long totalIn;
+ long totalOut;
+
+ unsigned char *outputBuf;
+ unsigned char *srcBuf;
+
+ unsigned long crc;
+ unsigned long srcCrc;
+ unsigned long srcSiz;
+ unsigned long srcConsumed;
+ unsigned long srcLen;
+ long outputBufPos;
+ long outputBufLen;
+
+ z_stream d_stream;
+}; // class GzipInputStream
+
+
+
+
+//#########################################################################
+//# G Z I P O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for gzip-compressing data going to the
+ * destination OutputStream
+ *
+ */
+class GzipOutputStream : public BasicOutputStream
+{
+
+public:
+
+ GzipOutputStream(OutputStream &destinationStream);
+
+ ~GzipOutputStream() override;
+
+ void close() override;
+
+ void flush() override;
+
+ int put(char ch) override;
+
+private:
+
+ std::vector<unsigned char> inputBuf;
+
+ long totalIn;
+ long totalOut;
+ unsigned long crc;
+
+}; // class GzipOutputStream
+
+
+
+
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+#endif /* __INKSCAPE_IO_GZIPSTREAM_H__ */
diff --git a/src/io/stream/inkscapestream.cpp b/src/io/stream/inkscapestream.cpp
new file mode 100644
index 0000000..bc6dc1d
--- /dev/null
+++ b/src/io/stream/inkscapestream.cpp
@@ -0,0 +1,800 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Our base input/output stream classes. These are is directly
+ * inherited from iostreams, and includes any extra
+ * functionality that we might need.
+ *
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstdlib>
+#include "inkscapestream.h"
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# U T I L I T Y
+//#########################################################################
+
+void pipeStream(InputStream &source, OutputStream &dest)
+{
+ for (;;)
+ {
+ int ch = source.get();
+ if (ch<0)
+ break;
+ dest.put(ch);
+ }
+ dest.flush();
+}
+
+//#########################################################################
+//# B A S I C I N P U T S T R E A M
+//#########################################################################
+
+
+/**
+ *
+ */
+BasicInputStream::BasicInputStream(InputStream &sourceStream)
+ : source(sourceStream)
+{
+ closed = false;
+}
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int BasicInputStream::available()
+{
+ if (closed)
+ return 0;
+ return source.available();
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void BasicInputStream::close()
+{
+ if (closed)
+ return;
+ source.close();
+ closed = true;
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int BasicInputStream::get()
+{
+ if (closed)
+ return -1;
+ return source.get();
+}
+
+
+
+//#########################################################################
+//# B A S I C O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ *
+ */
+BasicOutputStream::BasicOutputStream(OutputStream &destinationStream)
+ : destination(destinationStream)
+{
+ closed = false;
+}
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void BasicOutputStream::close()
+{
+ if (closed)
+ return;
+ destination.close();
+ closed = true;
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void BasicOutputStream::flush()
+{
+ if (closed)
+ return;
+ destination.flush();
+}
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int BasicOutputStream::put(char ch)
+{
+ if (closed)
+ return -1;
+ destination.put(ch);
+ return 1;
+}
+
+
+
+//#########################################################################
+//# B A S I C R E A D E R
+//#########################################################################
+/**
+ *
+ */
+BasicReader::BasicReader(Reader &sourceReader)
+{
+ source = &sourceReader;
+}
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this reader without blocking by the next caller of a method for
+ * this reader.
+ */
+int BasicReader::available()
+{
+ if (source)
+ return source->available();
+ else
+ return 0;
+}
+
+
+/**
+ * Closes this reader and releases any system resources
+ * associated with the reader.
+ */
+void BasicReader::close()
+{
+ if (source)
+ source->close();
+}
+
+/**
+ * Reads the next byte of data from the reader.
+ */
+char BasicReader::get()
+{
+ if (source)
+ return source->get();
+ else
+ return (char)-1;
+}
+
+
+/**
+ * Reads a line of data from the reader.
+ */
+Glib::ustring BasicReader::readLine()
+{
+ Glib::ustring str;
+ while (available() > 0)
+ {
+ char ch = get();
+ if (ch == '\n')
+ break;
+ str.push_back(ch);
+ }
+ return str;
+}
+
+/**
+ * Reads a line of data from the reader.
+ */
+Glib::ustring BasicReader::readWord()
+{
+ Glib::ustring str;
+ while (available() > 0)
+ {
+ char ch = get();
+ if (!std::isprint(ch))
+ break;
+ str.push_back(ch);
+ }
+ return str;
+}
+
+
+static bool getLong(Glib::ustring &str, long *val)
+{
+ const char *begin = str.raw().c_str();
+ char *end;
+ long ival = strtol(begin, &end, 10);
+ if (str == end)
+ return false;
+ *val = ival;
+ return true;
+}
+
+static bool getULong(Glib::ustring &str, unsigned long *val)
+{
+ const char *begin = str.raw().c_str();
+ char *end;
+ unsigned long ival = strtoul(begin, &end, 10);
+ if (str == end)
+ return false;
+ *val = ival;
+ return true;
+}
+
+static bool getDouble(Glib::ustring &str, double *val)
+{
+ const char *begin = str.raw().c_str();
+ char *end;
+ double ival = strtod(begin, &end);
+ if (str == end)
+ return false;
+ *val = ival;
+ return true;
+}
+
+
+
+const Reader &BasicReader::readBool (bool& val )
+{
+ Glib::ustring buf = readWord();
+ if (buf == "true")
+ val = true;
+ else
+ val = false;
+ return *this;
+}
+
+const Reader &BasicReader::readShort (short& val )
+{
+ Glib::ustring buf = readWord();
+ long ival;
+ if (getLong(buf, &ival))
+ val = (short) ival;
+ return *this;
+}
+
+const Reader &BasicReader::readUnsignedShort (unsigned short& val )
+{
+ Glib::ustring buf = readWord();
+ unsigned long ival;
+ if (getULong(buf, &ival))
+ val = (unsigned short) ival;
+ return *this;
+}
+
+const Reader &BasicReader::readInt (int& val )
+{
+ Glib::ustring buf = readWord();
+ long ival;
+ if (getLong(buf, &ival))
+ val = (int) ival;
+ return *this;
+}
+
+const Reader &BasicReader::readUnsignedInt (unsigned int& val )
+{
+ Glib::ustring buf = readWord();
+ unsigned long ival;
+ if (getULong(buf, &ival))
+ val = (unsigned int) ival;
+ return *this;
+}
+
+const Reader &BasicReader::readLong (long& val )
+{
+ Glib::ustring buf = readWord();
+ long ival;
+ if (getLong(buf, &ival))
+ val = ival;
+ return *this;
+}
+
+const Reader &BasicReader::readUnsignedLong (unsigned long& val )
+{
+ Glib::ustring buf = readWord();
+ unsigned long ival;
+ if (getULong(buf, &ival))
+ val = ival;
+ return *this;
+}
+
+const Reader &BasicReader::readFloat (float& val )
+{
+ Glib::ustring buf = readWord();
+ double ival;
+ if (getDouble(buf, &ival))
+ val = (float)ival;
+ return *this;
+}
+
+const Reader &BasicReader::readDouble (double& val )
+{
+ Glib::ustring buf = readWord();
+ double ival;
+ if (getDouble(buf, &ival))
+ val = ival;
+ return *this;
+}
+
+
+
+//#########################################################################
+//# I N P U T S T R E A M R E A D E R
+//#########################################################################
+
+
+InputStreamReader::InputStreamReader(InputStream &inputStreamSource)
+ : inputStream(inputStreamSource)
+{
+}
+
+
+
+/**
+ * Close the underlying OutputStream
+ */
+void InputStreamReader::close()
+{
+ inputStream.close();
+}
+
+/**
+ * Flush the underlying OutputStream
+ */
+int InputStreamReader::available()
+{
+ return inputStream.available();
+}
+
+/**
+ * Overloaded to receive its bytes from an InputStream
+ * rather than a Reader
+ */
+char InputStreamReader::get()
+{
+ char ch = inputStream.get();
+ return ch;
+}
+
+
+
+//#########################################################################
+//# S T D R E A D E R
+//#########################################################################
+
+
+/**
+ *
+ */
+StdReader::StdReader()
+{
+ inputStream = new StdInputStream();
+}
+
+/**
+ *
+ */
+StdReader::~StdReader()
+{
+ delete inputStream;
+}
+
+
+
+/**
+ * Close the underlying OutputStream
+ */
+void StdReader::close()
+{
+ inputStream->close();
+}
+
+/**
+ * Flush the underlying OutputStream
+ */
+int StdReader::available()
+{
+ return inputStream->available();
+}
+
+/**
+ * Overloaded to receive its bytes from an InputStream
+ * rather than a Reader
+ */
+char StdReader::get()
+{
+ char ch = inputStream->get();
+ return ch;
+}
+
+
+
+
+
+//#########################################################################
+//# B A S I C W R I T E R
+//#########################################################################
+
+/**
+ *
+ */
+BasicWriter::BasicWriter(Writer &destinationWriter)
+{
+ destination = &destinationWriter;
+}
+
+/**
+ * Closes this writer and releases any system resources
+ * associated with this writer.
+ */
+void BasicWriter::close()
+{
+ if (destination)
+ destination->close();
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void BasicWriter::flush()
+{
+ if (destination)
+ destination->flush();
+}
+
+/**
+ * Writes the specified byte to this output writer.
+ */
+void BasicWriter::put(char ch)
+{
+ if (destination)
+ destination->put(ch);
+}
+
+/**
+ * Provide printf()-like formatting
+ */
+Writer &BasicWriter::printf(char const *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ gchar *buf = g_strdup_vprintf(fmt, args);
+ va_end(args);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+/**
+ * Writes the specified character to this output writer.
+ */
+Writer &BasicWriter::writeChar(char ch)
+{
+ put(ch);
+ return *this;
+}
+
+
+/**
+ * Writes the specified unicode string to this output writer.
+ */
+Writer &BasicWriter::writeUString(const Glib::ustring &str)
+{
+ writeStdString(str.raw());
+ return *this;
+}
+
+/**
+ * Writes the specified standard string to this output writer.
+ */
+Writer &BasicWriter::writeStdString(const std::string &str)
+{
+ for (char it : str) {
+ put(it);
+ }
+ return *this;
+}
+
+/**
+ * Writes the specified character string to this output writer.
+ */
+Writer &BasicWriter::writeString(const char *str)
+{
+ std::string tmp;
+ if (str)
+ tmp = str;
+ else
+ tmp = "null";
+ writeStdString(tmp);
+ return *this;
+}
+
+
+
+
+/**
+ *
+ */
+Writer &BasicWriter::writeBool (bool val )
+{
+ if (val)
+ writeString("true");
+ else
+ writeString("false");
+ return *this;
+}
+
+
+/**
+ *
+ */
+Writer &BasicWriter::writeShort (short val )
+{
+ gchar *buf = g_strdup_printf("%d", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+
+
+/**
+ *
+ */
+Writer &BasicWriter::writeUnsignedShort (unsigned short val )
+{
+ gchar *buf = g_strdup_printf("%u", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeInt (int val)
+{
+ gchar *buf = g_strdup_printf("%d", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeUnsignedInt (unsigned int val)
+{
+ gchar *buf = g_strdup_printf("%u", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeLong (long val)
+{
+ gchar *buf = g_strdup_printf("%ld", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeUnsignedLong(unsigned long val)
+{
+ gchar *buf = g_strdup_printf("%lu", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeFloat(float val)
+{
+#if 1
+ gchar *buf = g_strdup_printf("%8.3f", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+#else
+ std::string tmp = ftos(val, 'g', 8, 3, 0);
+ writeStdString(tmp);
+#endif
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeDouble(double val)
+{
+#if 1
+ gchar *buf = g_strdup_printf("%8.3f", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+#else
+ std::string tmp = ftos(val, 'g', 8, 3, 0);
+ writeStdString(tmp);
+#endif
+ return *this;
+}
+
+
+Writer& operator<< (Writer &writer, char val)
+ { return writer.writeChar(val); }
+
+Writer& operator<< (Writer &writer, Glib::ustring &val)
+ { return writer.writeUString(val); }
+
+Writer& operator<< (Writer &writer, std::string &val)
+ { return writer.writeStdString(val); }
+
+Writer& operator<< (Writer &writer, char const *val)
+ { return writer.writeString(val); }
+
+Writer& operator<< (Writer &writer, bool val)
+ { return writer.writeBool(val); }
+
+Writer& operator<< (Writer &writer, short val)
+ { return writer.writeShort(val); }
+
+Writer& operator<< (Writer &writer, unsigned short val)
+ { return writer.writeUnsignedShort(val); }
+
+Writer& operator<< (Writer &writer, int val)
+ { return writer.writeInt(val); }
+
+Writer& operator<< (Writer &writer, unsigned int val)
+ { return writer.writeUnsignedInt(val); }
+
+Writer& operator<< (Writer &writer, long val)
+ { return writer.writeLong(val); }
+
+Writer& operator<< (Writer &writer, unsigned long val)
+ { return writer.writeUnsignedLong(val); }
+
+Writer& operator<< (Writer &writer, float val)
+ { return writer.writeFloat(val); }
+
+Writer& operator<< (Writer &writer, double val)
+ { return writer.writeDouble(val); }
+
+
+
+//#########################################################################
+//# O U T P U T S T R E A M W R I T E R
+//#########################################################################
+
+
+OutputStreamWriter::OutputStreamWriter(OutputStream &outputStreamDest)
+ : outputStream(outputStreamDest)
+{
+}
+
+
+
+/**
+ * Close the underlying OutputStream
+ */
+void OutputStreamWriter::close()
+{
+ flush();
+ outputStream.close();
+}
+
+/**
+ * Flush the underlying OutputStream
+ */
+void OutputStreamWriter::flush()
+{
+ outputStream.flush();
+}
+
+/**
+ * Overloaded to redirect the output chars from the next Writer
+ * in the chain to an OutputStream instead.
+ */
+void OutputStreamWriter::put(char ch)
+{
+ outputStream.put(ch);
+}
+
+//#########################################################################
+//# S T D W R I T E R
+//#########################################################################
+
+
+/**
+ *
+ */
+StdWriter::StdWriter()
+{
+ outputStream = new StdOutputStream();
+}
+
+
+/**
+ *
+ */
+StdWriter::~StdWriter()
+{
+ delete outputStream;
+}
+
+
+
+/**
+ * Close the underlying OutputStream
+ */
+void StdWriter::close()
+{
+ flush();
+ outputStream->close();
+}
+
+/**
+ * Flush the underlying OutputStream
+ */
+void StdWriter::flush()
+{
+ outputStream->flush();
+}
+
+/**
+ * Overloaded to redirect the output chars from the next Writer
+ * in the chain to an OutputStream instead.
+ */
+void StdWriter::put(char ch)
+{
+ outputStream->put(ch);
+}
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
diff --git a/src/io/stream/inkscapestream.h b/src/io/stream/inkscapestream.h
new file mode 100644
index 0000000..68c28ba
--- /dev/null
+++ b/src/io/stream/inkscapestream.h
@@ -0,0 +1,668 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_IO_INKSCAPESTREAM_H
+#define SEEN_INKSCAPE_IO_INKSCAPESTREAM_H
+/*
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstdio>
+#include <glibmm/ustring.h>
+
+namespace Inkscape
+{
+namespace IO
+{
+
+class StreamException : public std::exception
+{
+public:
+
+ StreamException(const char *theReason) noexcept
+ { reason = theReason; }
+ StreamException(Glib::ustring &theReason) noexcept
+ { reason = theReason; }
+ ~StreamException() noexcept override
+ = default;
+ char const *what() const noexcept override
+ { return reason.c_str(); }
+
+private:
+ Glib::ustring reason;
+
+};
+
+//#########################################################################
+//# I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This interface is the base of all input stream classes. Users who wish
+ * to make an InputStream that is part of a chain should inherit from
+ * BasicInputStream. Inherit from this class to make a source endpoint,
+ * such as a URI or buffer.
+ *
+ */
+class InputStream
+{
+
+public:
+
+ /**
+ * Constructor.
+ */
+ InputStream() = default;
+
+ /**
+ * Destructor
+ */
+ virtual ~InputStream() = default;
+
+ /**
+ * Return the number of bytes that are currently available
+ * to be read
+ */
+ virtual int available() = 0;
+
+ /**
+ * Do whatever it takes to 'close' this input stream
+ * The most likely implementation of this method will be
+ * for endpoints that use a resource for their data.
+ */
+ virtual void close() = 0;
+
+ /**
+ * Read one byte from this input stream. This is a blocking
+ * call. If no data is currently available, this call will
+ * not return until it exists. If the user does not want
+ * their code to block, then the usual solution is:
+ * if (available() > 0)
+ * myChar = get();
+ * This call returns -1 on end-of-file.
+ */
+ virtual int get() = 0;
+
+}; // class InputStream
+
+
+
+
+/**
+ * This is the class that most users should inherit, to provide
+ * their own streams.
+ *
+ */
+class BasicInputStream : public InputStream
+{
+
+public:
+
+ BasicInputStream(InputStream &sourceStream);
+
+ ~BasicInputStream() override = default;
+
+ int available() override;
+
+ void close() override;
+
+ int get() override;
+
+protected:
+
+ bool closed;
+
+ InputStream &source;
+
+private:
+
+
+}; // class BasicInputStream
+
+
+
+/**
+ * Convenience class for reading from standard input
+ */
+class StdInputStream : public InputStream
+{
+public:
+
+ int available() override
+ { return 0; }
+
+ void close() override
+ { /* do nothing */ }
+
+ int get() override
+ { return getchar(); }
+
+};
+
+
+
+
+
+
+//#########################################################################
+//# O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This interface is the base of all input stream classes. Users who wish
+ * to make an OutputStream that is part of a chain should inherit from
+ * BasicOutputStream. Inherit from this class to make a destination endpoint,
+ * such as a URI or buffer.
+ */
+class OutputStream
+{
+
+public:
+
+ /**
+ * Constructor.
+ */
+ OutputStream() = default;
+
+ /**
+ * Destructor
+ */
+ virtual ~OutputStream() = default;
+
+ /**
+ * This call should
+ * 1. flush itself
+ * 2. close itself
+ * 3. close the destination stream
+ */
+ virtual void close() = 0;
+
+ /**
+ * This call should push any pending data it might have to
+ * the destination stream. It should NOT call flush() on
+ * the destination stream.
+ */
+ virtual void flush() = 0;
+
+ /**
+ * Send one byte to the destination stream.
+ */
+ virtual int put(char ch) = 0;
+
+
+}; // class OutputStream
+
+
+/**
+ * This is the class that most users should inherit, to provide
+ * their own output streams.
+ */
+class BasicOutputStream : public OutputStream
+{
+
+public:
+
+ BasicOutputStream(OutputStream &destinationStream);
+
+ ~BasicOutputStream() override = default;
+
+ void close() override;
+
+ void flush() override;
+
+ int put(char ch) override;
+
+protected:
+
+ bool closed;
+
+ OutputStream &destination;
+
+
+}; // class BasicOutputStream
+
+
+
+/**
+ * Convenience class for writing to standard output
+ */
+class StdOutputStream : public OutputStream
+{
+public:
+
+ void close() override
+ { }
+
+ void flush() override
+ { }
+
+ int put(char ch) override
+ {return putchar(ch); }
+
+};
+
+
+
+
+//#########################################################################
+//# R E A D E R
+//#########################################################################
+
+
+/**
+ * This interface and its descendants are for unicode character-oriented input
+ *
+ */
+class Reader
+{
+
+public:
+
+ /**
+ * Constructor.
+ */
+ Reader() = default;
+
+ /**
+ * Destructor
+ */
+ virtual ~Reader() = default;
+
+
+ virtual int available() = 0;
+
+ virtual void close() = 0;
+
+ virtual char get() = 0;
+
+ virtual Glib::ustring readLine() = 0;
+
+ virtual Glib::ustring readWord() = 0;
+
+ /* Input formatting */
+ virtual const Reader& readBool (bool& val ) = 0;
+ virtual const Reader& operator>> (bool& val ) = 0;
+
+ virtual const Reader& readShort (short &val) = 0;
+ virtual const Reader& operator>> (short &val) = 0;
+
+ virtual const Reader& readUnsignedShort (unsigned short &val) = 0;
+ virtual const Reader& operator>> (unsigned short &val) = 0;
+
+ virtual const Reader& readInt (int &val) = 0;
+ virtual const Reader& operator>> (int &val) = 0;
+
+ virtual const Reader& readUnsignedInt (unsigned int &val) = 0;
+ virtual const Reader& operator>> (unsigned int &val) = 0;
+
+ virtual const Reader& readLong (long &val) = 0;
+ virtual const Reader& operator>> (long &val) = 0;
+
+ virtual const Reader& readUnsignedLong (unsigned long &val) = 0;
+ virtual const Reader& operator>> (unsigned long &val) = 0;
+
+ virtual const Reader& readFloat (float &val) = 0;
+ virtual const Reader& operator>> (float &val) = 0;
+
+ virtual const Reader& readDouble (double &val) = 0;
+ virtual const Reader& operator>> (double &val) = 0;
+
+}; // interface Reader
+
+
+
+/**
+ * This class and its descendants are for unicode character-oriented input
+ *
+ */
+class BasicReader : public Reader
+{
+
+public:
+
+ BasicReader(Reader &sourceStream);
+
+ ~BasicReader() override = default;
+
+ int available() override;
+
+ void close() override;
+
+ char get() override;
+
+ Glib::ustring readLine() override;
+
+ Glib::ustring readWord() override;
+
+ /* Input formatting */
+ const Reader& readBool (bool& val ) override;
+ const Reader& operator>> (bool& val ) override
+ { return readBool(val); }
+
+ const Reader& readShort (short &val) override;
+ const Reader& operator>> (short &val) override
+ { return readShort(val); }
+
+ const Reader& readUnsignedShort (unsigned short &val) override;
+ const Reader& operator>> (unsigned short &val) override
+ { return readUnsignedShort(val); }
+
+ const Reader& readInt (int &val) override;
+ const Reader& operator>> (int &val) override
+ { return readInt(val); }
+
+ const Reader& readUnsignedInt (unsigned int &val) override;
+ const Reader& operator>> (unsigned int &val) override
+ { return readUnsignedInt(val); }
+
+ const Reader& readLong (long &val) override;
+ const Reader& operator>> (long &val) override
+ { return readLong(val); }
+
+ const Reader& readUnsignedLong (unsigned long &val) override;
+ const Reader& operator>> (unsigned long &val) override
+ { return readUnsignedLong(val); }
+
+ const Reader& readFloat (float &val) override;
+ const Reader& operator>> (float &val) override
+ { return readFloat(val); }
+
+ const Reader& readDouble (double &val) override;
+ const Reader& operator>> (double &val) override
+ { return readDouble(val); }
+
+
+protected:
+
+ Reader *source;
+
+ BasicReader()
+ { source = nullptr; }
+
+private:
+
+}; // class BasicReader
+
+
+
+/**
+ * Class for placing a Reader on an open InputStream
+ *
+ */
+class InputStreamReader : public BasicReader
+{
+public:
+
+ InputStreamReader(InputStream &inputStreamSource);
+
+ /*Overload these 3 for your implementation*/
+ int available() override;
+
+ void close() override;
+
+ char get() override;
+
+
+private:
+
+ InputStream &inputStream;
+
+
+};
+
+/**
+ * Convenience class for reading formatted from standard input
+ *
+ */
+class StdReader : public BasicReader
+{
+public:
+
+ StdReader();
+
+ ~StdReader() override;
+
+ /*Overload these 3 for your implementation*/
+ int available() override;
+
+ void close() override;
+
+ char get() override;
+
+
+private:
+
+ InputStream *inputStream;
+
+
+};
+
+
+
+
+
+//#########################################################################
+//# W R I T E R
+//#########################################################################
+
+/**
+ * This interface and its descendants are for unicode character-oriented output
+ *
+ */
+class Writer
+{
+
+public:
+
+ /**
+ * Constructor.
+ */
+ Writer() = default;
+
+ /**
+ * Destructor
+ */
+ virtual ~Writer() = default;
+
+ virtual void close() = 0;
+
+ virtual void flush() = 0;
+
+ virtual void put(char ch) = 0;
+
+ /* Formatted output */
+ virtual Writer& printf(char const *fmt, ...) G_GNUC_PRINTF(2,3) = 0;
+
+ virtual Writer& writeChar(char val) = 0;
+
+ virtual Writer& writeUString(const Glib::ustring &val) = 0;
+
+ virtual Writer& writeStdString(const std::string &val) = 0;
+
+ virtual Writer& writeString(const char *str) = 0;
+
+ virtual Writer& writeBool (bool val ) = 0;
+
+ virtual Writer& writeShort (short val ) = 0;
+
+ virtual Writer& writeUnsignedShort (unsigned short val ) = 0;
+
+ virtual Writer& writeInt (int val ) = 0;
+
+ virtual Writer& writeUnsignedInt (unsigned int val ) = 0;
+
+ virtual Writer& writeLong (long val ) = 0;
+
+ virtual Writer& writeUnsignedLong (unsigned long val ) = 0;
+
+ virtual Writer& writeFloat (float val ) = 0;
+
+ virtual Writer& writeDouble (double val ) = 0;
+
+
+
+}; // interface Writer
+
+
+/**
+ * This class and its descendants are for unicode character-oriented output
+ *
+ */
+class BasicWriter : public Writer
+{
+
+public:
+
+ BasicWriter(Writer &destinationWriter);
+
+ ~BasicWriter() override = default;
+
+ /*Overload these 3 for your implementation*/
+ void close() override;
+
+ void flush() override;
+
+ void put(char ch) override;
+
+
+
+ /* Formatted output */
+ Writer &printf(char const *fmt, ...) override G_GNUC_PRINTF(2,3);
+
+ Writer& writeChar(char val) override;
+
+ Writer& writeUString(const Glib::ustring &val) override;
+
+ Writer& writeStdString(const std::string &val) override;
+
+ Writer& writeString(const char *str) override;
+
+ Writer& writeBool (bool val ) override;
+
+ Writer& writeShort (short val ) override;
+
+ Writer& writeUnsignedShort (unsigned short val ) override;
+
+ Writer& writeInt (int val ) override;
+
+ Writer& writeUnsignedInt (unsigned int val ) override;
+
+ Writer& writeLong (long val ) override;
+
+ Writer& writeUnsignedLong (unsigned long val ) override;
+
+ Writer& writeFloat (float val ) override;
+
+ Writer& writeDouble (double val ) override;
+
+
+protected:
+
+ Writer *destination;
+
+ BasicWriter()
+ { destination = nullptr; }
+
+private:
+
+}; // class BasicWriter
+
+
+
+Writer& operator<< (Writer &writer, char val);
+
+Writer& operator<< (Writer &writer, Glib::ustring &val);
+
+Writer& operator<< (Writer &writer, std::string &val);
+
+Writer& operator<< (Writer &writer, char const *val);
+
+Writer& operator<< (Writer &writer, bool val);
+
+Writer& operator<< (Writer &writer, short val);
+
+Writer& operator<< (Writer &writer, unsigned short val);
+
+Writer& operator<< (Writer &writer, int val);
+
+Writer& operator<< (Writer &writer, unsigned int val);
+
+Writer& operator<< (Writer &writer, long val);
+
+Writer& operator<< (Writer &writer, unsigned long val);
+
+Writer& operator<< (Writer &writer, float val);
+
+Writer& operator<< (Writer &writer, double val);
+
+
+
+
+/**
+ * Class for placing a Writer on an open OutputStream
+ *
+ */
+class OutputStreamWriter : public BasicWriter
+{
+public:
+
+ OutputStreamWriter(OutputStream &outputStreamDest);
+
+ /*Overload these 3 for your implementation*/
+ void close() override;
+
+ void flush() override;
+
+ void put(char ch) override;
+
+
+private:
+
+ OutputStream &outputStream;
+
+
+};
+
+
+/**
+ * Convenience class for writing to standard output
+ */
+class StdWriter : public BasicWriter
+{
+public:
+ StdWriter();
+
+ ~StdWriter() override;
+
+
+ void close() override;
+
+
+ void flush() override;
+
+
+ void put(char ch) override;
+
+
+private:
+
+ OutputStream *outputStream;
+
+};
+
+//#########################################################################
+//# U T I L I T Y
+//#########################################################################
+
+void pipeStream(InputStream &source, OutputStream &dest);
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+#endif // SEEN_INKSCAPE_IO_INKSCAPESTREAM_H
diff --git a/src/io/stream/streamtest.cpp b/src/io/stream/streamtest.cpp
new file mode 100644
index 0000000..5d27058
--- /dev/null
+++ b/src/io/stream/streamtest.cpp
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2015 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include <stdio.h>
+#include <limits.h> // realpath
+#include <stdlib.h> // mkdtemp, realpath
+#include <unistd.h> // chdir
+#include <string.h> // strlen, strncpy, strrchr
+
+#include "inkscapestream.h"
+#include "base64stream.h"
+#include "gzipstream.h"
+#include "stringstream.h"
+#include "uristream.h"
+#include "xsltstream.h"
+
+// quick way to pass the name of the executable into the test
+char myself[PATH_MAX];
+
+// names and path storage for other tests
+char *xmlname = "crystalegg.xml";
+char xmlpath[PATH_MAX];
+char *xslname = "doc2html.xsl";
+char xslpath[PATH_MAX];
+
+bool testUriStream()
+{
+ printf("######### UriStream copy ############\n");
+ Inkscape::URI inUri(myself);
+ Inkscape::IO::UriInputStream ins(inUri);
+ Inkscape::URI outUri("streamtest.copy");
+ Inkscape::IO::UriOutputStream outs(outUri);
+
+ pipeStream(ins, outs);
+
+ ins.close();
+ outs.close();
+
+ return true;
+}
+
+bool testWriter()
+{
+ printf("######### OutputStreamWriter ############\n");
+ Inkscape::IO::StdOutputStream outs;
+ Inkscape::IO::OutputStreamWriter writer(outs);
+
+ writer << "Hello, world! " << 123.45 << " times\n";
+
+ writer.printf("There are %f quick brown foxes in %d states\n", 123.45, 88);
+
+ return true;
+}
+
+bool testStdWriter()
+{
+ printf("######### StdWriter ############\n");
+ Inkscape::IO::StdWriter writer;
+
+ writer << "Hello, world! " << 123.45 << " times\n";
+
+ writer.printf("There are %f quick brown foxes in %d states\n", 123.45, 88);
+
+ return true;
+}
+
+bool testBase64()
+{
+ printf("######### Base64 Out ############\n");
+ Inkscape::URI plainInUri(xmlpath);
+ Inkscape::IO::UriInputStream ins1(plainInUri);
+
+ Inkscape::URI b64OutUri("crystalegg.xml.b64");
+ Inkscape::IO::UriOutputStream outs1(b64OutUri);
+ Inkscape::IO::Base64OutputStream b64Outs(outs1);
+
+ pipeStream(ins1, b64Outs);
+
+ ins1.close();
+ b64Outs.close();
+
+ printf("######### Base64 In ############\n");
+ Inkscape::URI b64InUri("crystalegg.xml.b64");
+ Inkscape::IO::UriInputStream ins2(b64InUri);
+ Inkscape::IO::Base64InputStream b64Ins(ins2);
+
+ Inkscape::URI plainOutUri("crystalegg.xml.b64dec");
+ Inkscape::IO::UriOutputStream outs2(plainOutUri);
+
+ pipeStream(b64Ins, outs2);
+
+ outs2.close();
+ b64Ins.close();
+
+ return true;
+}
+
+bool testXslt()
+{
+ printf("######### XSLT Sheet ############\n");
+ Inkscape::URI xsltSheetUri(xslpath);
+ Inkscape::IO::UriInputStream xsltSheetIns(xsltSheetUri);
+ Inkscape::IO::XsltStyleSheet stylesheet(xsltSheetIns);
+ xsltSheetIns.close();
+
+ Inkscape::URI sourceUri(xmlpath);
+ Inkscape::IO::UriInputStream xmlIns(sourceUri);
+
+ printf("######### XSLT Input ############\n");
+ Inkscape::URI destUri("test.html");
+ Inkscape::IO::UriOutputStream xmlOuts(destUri);
+
+ Inkscape::IO::XsltInputStream xsltIns(xmlIns, stylesheet);
+ pipeStream(xsltIns, xmlOuts);
+ xsltIns.close();
+ xmlOuts.close();
+
+
+ printf("######### XSLT Output ############\n");
+
+ Inkscape::IO::UriInputStream xmlIns2(sourceUri);
+
+ Inkscape::URI destUri2("test2.html");
+ Inkscape::IO::UriOutputStream xmlOuts2(destUri2);
+
+ Inkscape::IO::XsltOutputStream xsltOuts(xmlOuts2, stylesheet);
+ pipeStream(xmlIns2, xsltOuts);
+ xmlIns2.close();
+ xsltOuts.close();
+
+ return true;
+}
+
+bool testGzip()
+{
+
+ printf("######### Gzip Output ############\n");
+ Inkscape::URI gzUri("test.gz");
+ Inkscape::URI sourceUri(xmlpath);
+ Inkscape::IO::UriInputStream sourceIns(sourceUri);
+ Inkscape::IO::UriOutputStream gzOuts(gzUri);
+
+ Inkscape::IO::GzipOutputStream gzipOuts(gzOuts);
+ pipeStream(sourceIns, gzipOuts);
+ sourceIns.close();
+ gzipOuts.close();
+
+ printf("######### Gzip Input ############\n");
+
+ Inkscape::IO::UriInputStream gzIns(gzUri);
+ Inkscape::URI destUri("crystalegg2.xml");
+ Inkscape::IO::UriOutputStream destOuts(destUri);
+
+ Inkscape::IO::GzipInputStream gzipIns(gzIns);
+ pipeStream(gzipIns, destOuts);
+ gzipIns.close();
+ destOuts.close();
+
+ return true;
+}
+
+bool doTest()
+{
+ if (!testUriStream())
+ {
+ return false;
+ }
+ if (!testWriter())
+ {
+ return false;
+ }
+ if (!testStdWriter())
+ {
+ return false;
+ }
+ if (!testBase64())
+ {
+ return false;
+ }
+ if (!testXslt())
+ {
+ return false;
+ }
+ if (!testGzip())
+ {
+ return false;
+ }
+ return true;
+}
+
+void path_init(char *path, char *name)
+{
+ if (strlen(name)>PATH_MAX-strlen(myself))
+ {
+ printf("merging paths would be too long\n");
+ exit(1);
+ }
+ strncpy(path,myself,PATH_MAX);
+ char * ptr = strrchr(path,'/');
+ if (!ptr)
+ {
+ printf("path '%s' is missing any slashes\n",path);
+ exit(1);
+ }
+ strncpy(ptr+1,name,strlen(name)+1);
+ path[PATH_MAX-1] = '\0';
+ printf("'%s'\n",path);
+}
+
+
+int main(int argc, char **argv)
+{
+ if (!realpath(argv[0],myself))
+ {
+ perror("realpath");
+ return 1;
+ }
+ path_init(xmlpath,xmlname);
+ path_init(xslpath,xslname);
+
+ // create temp files somewhere else instead of current dir
+ // TODO: clean them up too
+ char * testpath = strdup("/tmp/streamtest-XXXXXX");
+ char * testpath2;
+ testpath2 = mkdtemp(testpath);
+ free(testpath);
+ if (!testpath2)
+ {
+ perror("mkdtemp");
+ return 1;
+ }
+ if (chdir(testpath2))
+ {
+ perror("chdir");
+ return 1;
+ }
+
+ if (!doTest())
+ {
+ printf("#### Test failed\n");
+ return 1;
+ }
+ else
+ {
+ printf("##### Test succeeded\n");
+ }
+ return 0;
+}
diff --git a/src/io/stream/stringstream.cpp b/src/io/stream/stringstream.cpp
new file mode 100644
index 0000000..0259869
--- /dev/null
+++ b/src/io/stream/stringstream.cpp
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Our base String stream classes. We implement these to
+ * be based on Glib::ustring
+ *
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "stringstream.h"
+
+namespace Inkscape
+{
+namespace IO
+{
+
+
+//#########################################################################
+//# S T R I N G I N P U T S T R E A M
+//#########################################################################
+
+
+/**
+ *
+ */
+StringInputStream::StringInputStream(Glib::ustring &sourceString)
+ : buffer(sourceString)
+{
+ position = 0;
+}
+
+/**
+ *
+ */
+StringInputStream::~StringInputStream()
+= default;
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int StringInputStream::available()
+{
+ return buffer.size() - position;
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void StringInputStream::close()
+{
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int StringInputStream::get()
+{
+ if (position >= (int)buffer.size())
+ return -1;
+ int ch = (int) buffer[position++];
+ return ch;
+}
+
+
+
+
+//#########################################################################
+//# S T R I N G O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ *
+ */
+StringOutputStream::StringOutputStream()
+= default;
+
+/**
+ *
+ */
+StringOutputStream::~StringOutputStream()
+= default;
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void StringOutputStream::close()
+{
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void StringOutputStream::flush()
+{
+ //nothing to do
+}
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int StringOutputStream::put(char ch)
+{
+ buffer.push_back(ch);
+ return 1;
+}
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
diff --git a/src/io/stream/stringstream.h b/src/io/stream/stringstream.h
new file mode 100644
index 0000000..3afb9a8
--- /dev/null
+++ b/src/io/stream/stringstream.h
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef __INKSCAPE_IO_STRINGSTREAM_H__
+#define __INKSCAPE_IO_STRINGSTREAM_H__
+
+#include <glibmm/ustring.h>
+
+#include "inkscapestream.h"
+
+
+namespace Inkscape
+{
+namespace IO
+{
+
+
+//#########################################################################
+//# S T R I N G I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for reading character from a Glib::ustring
+ *
+ */
+class StringInputStream : public InputStream
+{
+
+public:
+
+ StringInputStream(Glib::ustring &sourceString);
+
+ ~StringInputStream() override;
+
+ int available() override;
+
+ void close() override;
+
+ int get() override;
+
+private:
+
+ Glib::ustring &buffer;
+
+ long position;
+
+}; // class StringInputStream
+
+
+
+
+//#########################################################################
+//# S T R I N G O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for sending a stream to a Glib::ustring
+ *
+ */
+class StringOutputStream : public OutputStream
+{
+
+public:
+
+ StringOutputStream();
+
+ ~StringOutputStream() override;
+
+ void close() override;
+
+ void flush() override;
+
+ int put(char ch) override;
+
+ virtual Glib::ustring &getString()
+ { return buffer; }
+
+ virtual void clear()
+ { buffer = ""; }
+
+private:
+
+ Glib::ustring buffer;
+
+
+}; // class StringOutputStream
+
+
+
+
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+
+#endif /* __INKSCAPE_IO_STRINGSTREAM_H__ */
diff --git a/src/io/stream/uristream.cpp b/src/io/stream/uristream.cpp
new file mode 100644
index 0000000..1596919
--- /dev/null
+++ b/src/io/stream/uristream.cpp
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Our base String stream classes. We implement these to
+ * be based on Glib::ustring
+ *
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "uristream.h"
+#include "io/sys.h"
+#include <string>
+#include <cstring>
+
+
+namespace Inkscape
+{
+namespace IO
+{
+
+/*
+ * URI scheme types
+ */
+#define SCHEME_NONE 0
+#define SCHEME_FILE 1
+#define SCHEME_DATA 2
+
+/*
+ * A temporary modification of Jon Cruz's portable fopen().
+ * Simplified a bit, since we will always use binary
+*/
+
+#define FILE_READ 1
+#define FILE_WRITE 2
+
+static FILE *fopen_utf8name( char const *utf8name, int mode )
+{
+ FILE *fp = nullptr;
+ if (!utf8name)
+ {
+ return nullptr;
+ }
+ if (mode!=FILE_READ && mode!=FILE_WRITE)
+ {
+ return nullptr;
+ }
+
+#ifndef _WIN32
+ gchar *filename = g_filename_from_utf8( utf8name, -1, nullptr, nullptr, nullptr );
+ if ( filename ) {
+ if (mode == FILE_READ)
+ fp = std::fopen(filename, "rb");
+ else
+ fp = std::fopen(filename, "wb");
+ g_free(filename);
+ }
+#else
+ {
+ gunichar2 *wideName = g_utf8_to_utf16( utf8name, -1, NULL, NULL, NULL );
+ if ( wideName ) {
+ if (mode == FILE_READ)
+ fp = _wfopen( (wchar_t*)wideName, L"rb" );
+ else
+ fp = _wfopen( (wchar_t*)wideName, L"wb" );
+ g_free( wideName );
+ } else {
+ gchar *safe = Inkscape::IO::sanitizeString(utf8name);
+ g_message("Unable to convert filename from UTF-8 to UTF-16 [%s]", safe);
+ g_free(safe);
+ }
+ }
+#endif
+
+ return fp;
+}
+
+
+
+//#########################################################################
+//# F I L E I N P U T S T R E A M
+//#########################################################################
+
+
+/**
+ *
+ */
+FileInputStream::FileInputStream(FILE *source)
+ : inf(source)
+{
+ if (!inf) {
+ Glib::ustring err = "FileInputStream passed NULL";
+ throw StreamException(err);
+ }
+}
+
+/**
+ *
+ */
+FileInputStream::~FileInputStream()
+{
+ close();
+}
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int FileInputStream::available()
+{
+ return 0;
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void FileInputStream::close()
+{
+ if (!inf)
+ return;
+ fflush(inf);
+ fclose(inf);
+ inf=nullptr;
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int FileInputStream::get()
+{
+ int retVal = -1;
+ if (!inf || feof(inf))
+ {
+ retVal = -1;
+ }
+ else
+ {
+ retVal = fgetc(inf);
+ }
+
+ return retVal;
+}
+
+
+
+
+//#########################################################################
+//# F I L E O U T P U T S T R E A M
+//#########################################################################
+
+FileOutputStream::FileOutputStream(FILE *fp)
+ : ownsFile(false)
+ , outf(fp)
+{
+ if (!outf) {
+ Glib::ustring err = "FileOutputStream given null file ";
+ throw StreamException(err);
+ }
+}
+
+/**
+ *
+ */
+FileOutputStream::~FileOutputStream()
+{
+ close();
+}
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void FileOutputStream::close()
+{
+ if (!outf)
+ return;
+ fflush(outf);
+ if ( ownsFile )
+ fclose(outf);
+ outf=nullptr;
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void FileOutputStream::flush()
+{
+ if (!outf)
+ return;
+ fflush(outf);
+}
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int FileOutputStream::put(char ch)
+{
+ unsigned char uch;
+
+ if (!outf)
+ return -1;
+ uch = (unsigned char)(ch & 0xff);
+ if (fputc(uch, outf) == EOF) {
+ Glib::ustring err = "ERROR writing to file ";
+ throw StreamException(err);
+ }
+
+ return 1;
+}
+
+
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/stream/uristream.h b/src/io/stream/uristream.h
new file mode 100644
index 0000000..f0544d8
--- /dev/null
+++ b/src/io/stream/uristream.h
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_IO_URISTREAM_H
+#define SEEN_INKSCAPE_IO_URISTREAM_H
+/**
+ * @file
+ * This should be the only way that we provide sources/sinks
+ * to any input/output stream.
+ */
+/*
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "object/uri.h"
+
+#include "inkscapestream.h"
+
+
+namespace Inkscape
+{
+
+class URI;
+
+namespace IO
+{
+
+//#########################################################################
+//# F I L E I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for receiving a stream of data from a file
+ */
+class FileInputStream : public InputStream
+{
+
+public:
+ FileInputStream(FILE *source);
+
+ ~FileInputStream() override;
+
+ int available() override;
+
+ void close() override;
+
+ int get() override;
+
+private:
+ FILE *inf; //for file: uris
+
+}; // class FileInputStream
+
+
+
+
+//#########################################################################
+//# F I L E O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for sending a stream to a destination file
+ */
+class FileOutputStream : public OutputStream
+{
+
+public:
+
+ FileOutputStream(FILE *fp);
+
+ ~FileOutputStream() override;
+
+ void close() override;
+
+ void flush() override;
+
+ int put(char ch) override;
+
+private:
+
+ bool ownsFile;
+
+ FILE *outf; //for file: uris
+
+}; // class FileOutputStream
+
+
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+#endif // SEEN_INKSCAPE_IO_URISTREAM_H
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/stream/xsltstream.cpp b/src/io/stream/xsltstream.cpp
new file mode 100644
index 0000000..882db30
--- /dev/null
+++ b/src/io/stream/xsltstream.cpp
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * XSL Transforming input and output classes
+ *
+ * Authors:
+ * Bob Jamison <ishmalius@gmail.com>
+ *
+ * Copyright (C) 2004-2008 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "xsltstream.h"
+#include "stringstream.h"
+#include <libxslt/transform.h>
+
+
+
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# X S L T S T Y L E S H E E T
+//#########################################################################
+
+/**
+ *
+ */
+XsltStyleSheet::XsltStyleSheet(InputStream &xsltSource)
+
+ : stylesheet(nullptr)
+{
+ if (!read(xsltSource)) {
+ throw StreamException("read failed");
+ }
+}
+
+/**
+ *
+ */
+XsltStyleSheet::XsltStyleSheet()
+ : stylesheet(nullptr)
+{
+}
+
+
+
+/**
+ *
+ */
+bool XsltStyleSheet::read(InputStream &xsltSource)
+{
+ StringOutputStream outs;
+ pipeStream(xsltSource, outs);
+ std::string strBuf = outs.getString().raw();
+ xmlDocPtr doc = xmlParseMemory(strBuf.c_str(), strBuf.size());
+ stylesheet = xsltParseStylesheetDoc(doc);
+ //following not necessary. handled by xsltFreeStylesheet(stylesheet);
+ //xmlFreeDoc(doc);
+ if (!stylesheet)
+ return false;
+ return true;
+}
+
+
+/**
+ *
+ */
+XsltStyleSheet::~XsltStyleSheet()
+{
+ if (stylesheet)
+ xsltFreeStylesheet(stylesheet);
+}
+
+
+
+//#########################################################################
+//# X S L T I N P U T S T R E A M
+//#########################################################################
+
+
+/**
+ *
+ */
+XsltInputStream::XsltInputStream(InputStream &xmlSource, XsltStyleSheet &sheet)
+ : BasicInputStream(xmlSource), stylesheet(sheet)
+{
+ //Load the data
+ StringOutputStream outs;
+ pipeStream(source, outs);
+ std::string strBuf = outs.getString().raw();
+
+ //Do the processing
+ const char *params[1];
+ params[0] = nullptr;
+ xmlDocPtr srcDoc = xmlParseMemory(strBuf.c_str(), strBuf.size());
+ xmlDocPtr resDoc = xsltApplyStylesheet(stylesheet.stylesheet, srcDoc, params);
+ xmlDocDumpFormatMemory(resDoc, &outbuf, &outsize, 1);
+ outpos = 0;
+
+ //Free our mem
+ xmlFreeDoc(resDoc);
+ xmlFreeDoc(srcDoc);
+}
+
+/**
+ *
+ */
+XsltInputStream::~XsltInputStream()
+{
+ xmlFree(outbuf);
+}
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int XsltInputStream::available()
+{
+ return outsize - outpos;
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void XsltInputStream::close()
+{
+ closed = true;
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int XsltInputStream::get()
+{
+ if (closed)
+ return -1;
+ if (outpos >= outsize)
+ return -1;
+ int ch = (int) outbuf[outpos++];
+ return ch;
+}
+
+
+
+
+
+
+//#########################################################################
+//# X S L T O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ *
+ */
+XsltOutputStream::XsltOutputStream(OutputStream &dest, XsltStyleSheet &sheet)
+ : BasicOutputStream(dest), stylesheet(sheet)
+{
+ flushed = false;
+}
+
+/**
+ *
+ */
+XsltOutputStream::~XsltOutputStream()
+{
+ //do not automatically close
+}
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void XsltOutputStream::close()
+{
+ flush();
+ destination.close();
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void XsltOutputStream::flush()
+{
+ if (flushed)
+ {
+ destination.flush();
+ return;
+ }
+
+ //Do the processing
+ xmlChar *resbuf;
+ int resSize;
+ const char *params[1];
+ params[0] = nullptr;
+ xmlDocPtr srcDoc = xmlParseMemory(outbuf.raw().c_str(), outbuf.size());
+ xmlDocPtr resDoc = xsltApplyStylesheet(stylesheet.stylesheet, srcDoc, params);
+ xmlDocDumpFormatMemory(resDoc, &resbuf, &resSize, 1);
+ /*
+ xmlErrorPtr err = xmlGetLastError();
+ if (err)
+ {
+ throw StreamException(err->message);
+ }
+ */
+
+ for (int i=0 ; i<resSize ; i++)
+ {
+ char ch = resbuf[i];
+ destination.put(ch);
+ }
+
+ //Free our mem
+ xmlFree(resbuf);
+ xmlFreeDoc(resDoc);
+ xmlFreeDoc(srcDoc);
+ destination.flush();
+ flushed = true;
+}
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int XsltOutputStream::put(char ch)
+{
+ outbuf.push_back(ch);
+ return 1;
+}
+
+
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
diff --git a/src/io/stream/xsltstream.h b/src/io/stream/xsltstream.h
new file mode 100644
index 0000000..7410ddc
--- /dev/null
+++ b/src/io/stream/xsltstream.h
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_IO_XSLTSTREAM_H
+#define SEEN_INKSCAPE_IO_XSLTSTREAM_H
+/**
+ * @file
+ * Xslt-enabled input and output streams.
+ */
+/*
+ * Authors:
+ * Bob Jamison <ishmalius@gmail.com>
+ *
+ * Copyright (C) 2004-2008 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "inkscapestream.h"
+
+#include <libxslt/xslt.h>
+#include <libxslt/xsltInternals.h>
+
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# X S L T S T Y L E S H E E T
+//#########################################################################
+/**
+ * This is a container for reusing a loaded stylesheet
+ */
+class XsltStyleSheet
+{
+
+public:
+
+ /**
+ * Constructor with loading
+ */
+ XsltStyleSheet(InputStream &source);
+
+ /**
+ * Simple constructor, no loading
+ */
+ XsltStyleSheet();
+
+ /**
+ * Loader
+ */
+ bool read(InputStream &source);
+
+ /**
+ * Destructor
+ */
+ virtual ~XsltStyleSheet();
+
+ xsltStylesheetPtr stylesheet;
+
+
+}; // class XsltStyleSheet
+
+
+//#########################################################################
+//# X S L T I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for transforming stream input by a given stylesheet
+ */
+class XsltInputStream : public BasicInputStream
+{
+
+public:
+
+ XsltInputStream(InputStream &xmlSource, XsltStyleSheet &stylesheet);
+
+ ~XsltInputStream() override;
+
+ int available() override;
+
+ void close() override;
+
+ int get() override;
+
+
+private:
+
+ XsltStyleSheet &stylesheet;
+
+ xmlChar *outbuf;
+ int outsize;
+ int outpos;
+
+}; // class XsltInputStream
+
+
+
+
+//#########################################################################
+//# X S L T O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for transforming stream output by a given stylesheet
+ */
+class XsltOutputStream : public BasicOutputStream
+{
+
+public:
+
+ XsltOutputStream(OutputStream &destination, XsltStyleSheet &stylesheet);
+
+ ~XsltOutputStream() override;
+
+ void close() override;
+
+ void flush() override;
+
+ int put(char ch) override;
+
+private:
+
+ XsltStyleSheet &stylesheet;
+
+ Glib::ustring outbuf;
+
+ bool flushed;
+
+}; // class XsltOutputStream
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+#endif /* __INKSCAPE_IO_XSLTSTREAM_H__ */
diff --git a/src/io/sys.cpp b/src/io/sys.cpp
new file mode 100644
index 0000000..ee1956f
--- /dev/null
+++ b/src/io/sys.cpp
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ * System abstraction utility routines
+ *
+ * Authors:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2004-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include <fstream>
+#ifdef _WIN32
+#include <fcntl.h>
+#include <io.h>
+#endif
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glibmm/ustring.h>
+#include <gtk/gtk.h>
+
+#include "preferences.h"
+#include "sys.h"
+
+//#define INK_DUMP_FILENAME_CONV 1
+#undef INK_DUMP_FILENAME_CONV
+
+//#define INK_DUMP_FOPEN 1
+#undef INK_DUMP_FOPEN
+
+void dump_str(gchar const *str, gchar const *prefix);
+void dump_ustr(Glib::ustring const &ustr);
+
+extern guint update_in_progress;
+
+
+void Inkscape::IO::dump_fopen_call( char const *utf8name, char const *id )
+{
+#ifdef INK_DUMP_FOPEN
+ Glib::ustring str;
+ for ( int i = 0; utf8name[i]; i++ )
+ {
+ if ( utf8name[i] == '\\' )
+ {
+ str += "\\\\";
+ }
+ else if ( (utf8name[i] >= 0x20) && ((0x0ff & utf8name[i]) <= 0x7f) )
+ {
+ str += utf8name[i];
+ }
+ else
+ {
+ gchar tmp[32];
+ g_snprintf( tmp, sizeof(tmp), "\\x%02x", (0x0ff & utf8name[i]) );
+ str += tmp;
+ }
+ }
+ g_message( "fopen call %s for [%s]", id, str.data() );
+#else
+ (void)utf8name;
+ (void)id;
+#endif
+}
+
+FILE *Inkscape::IO::fopen_utf8name( char const *utf8name, char const *mode )
+{
+ FILE* fp = nullptr;
+
+ if (Glib::ustring( utf8name ) == Glib::ustring("-")) {
+ // user requests to use pipes
+
+ Glib::ustring how( mode );
+ if ( how.find("w") != Glib::ustring::npos ) {
+#ifdef _WIN32
+ setmode(fileno(stdout), O_BINARY);
+#endif
+ return stdout;
+ } else {
+ return stdin;
+ }
+ }
+
+ gchar *filename = g_filename_from_utf8( utf8name, -1, nullptr, nullptr, nullptr );
+ if ( filename )
+ {
+ // ensure we open the file in binary mode (not needed in POSIX but doesn't hurt either)
+ Glib::ustring how( mode );
+ if ( how.find("b") == Glib::ustring::npos )
+ {
+ how.append("b");
+ }
+ // when opening a file for writing: create parent directories if they don't exist already
+ if ( how.find("w") != Glib::ustring::npos )
+ {
+ gchar *dirname = g_path_get_dirname(utf8name);
+ if (g_mkdir_with_parents(dirname, 0777)) {
+ g_warning("Could not create directory '%s'", dirname);
+ }
+ g_free(dirname);
+ }
+ fp = g_fopen(filename, how.c_str());
+ g_free(filename);
+ filename = nullptr;
+ }
+ return fp;
+}
+
+
+int Inkscape::IO::mkdir_utf8name( char const *utf8name )
+{
+ int retval = -1;
+ gchar *filename = g_filename_from_utf8( utf8name, -1, nullptr, nullptr, nullptr );
+ if ( filename )
+ {
+ retval = g_mkdir(filename, S_IRWXU | S_IRGRP | S_IXGRP); // The mode argument is ignored on Windows.
+ g_free(filename);
+ filename = nullptr;
+ }
+ return retval;
+}
+
+bool Inkscape::IO::file_test( char const *utf8name, GFileTest test )
+{
+ bool exists = false;
+
+ // in case the file to check is a pipe it doesn't need to exist
+ if (g_strcmp0(utf8name, "-") == 0 && G_FILE_TEST_IS_REGULAR)
+ return true;
+
+ if ( utf8name ) {
+ gchar *filename = nullptr;
+ if (utf8name && !g_utf8_validate(utf8name, -1, nullptr)) {
+ /* FIXME: Trying to guess whether or not a filename is already in utf8 is unreliable.
+ If any callers pass non-utf8 data (e.g. using g_get_home_dir), then change caller to
+ use simple g_file_test. Then add g_return_val_if_fail(g_utf_validate(...), false)
+ to beginning of this function. */
+ filename = g_strdup(utf8name);
+ // Looks like g_get_home_dir isn't safe.
+ //g_warning("invalid UTF-8 detected internally. HUNT IT DOWN AND KILL IT!!!");
+ } else {
+ filename = g_filename_from_utf8 ( utf8name, -1, nullptr, nullptr, nullptr );
+ }
+ if ( filename ) {
+ exists = g_file_test (filename, test);
+ g_free(filename);
+ filename = nullptr;
+ } else {
+ g_warning( "Unable to convert filename in IO:file_test" );
+ }
+ }
+
+ return exists;
+}
+
+bool Inkscape::IO::file_is_writable( char const *utf8name)
+{
+ bool success = true;
+
+ if ( utf8name) {
+ gchar *filename = nullptr;
+ if (utf8name && !g_utf8_validate(utf8name, -1, nullptr)) {
+ /* FIXME: Trying to guess whether or not a filename is already in utf8 is unreliable.
+ If any callers pass non-utf8 data (e.g. using g_get_home_dir), then change caller to
+ use simple g_file_test. Then add g_return_val_if_fail(g_utf_validate(...), false)
+ to beginning of this function. */
+ filename = g_strdup(utf8name);
+ // Looks like g_get_home_dir isn't safe.
+ //g_warning("invalid UTF-8 detected internally. HUNT IT DOWN AND KILL IT!!!");
+ } else {
+ filename = g_filename_from_utf8 ( utf8name, -1, nullptr, nullptr, nullptr );
+ }
+ if ( filename ) {
+ GStatBuf st;
+ if (g_file_test (filename, G_FILE_TEST_EXISTS)){
+ if (g_lstat (filename, &st) == 0) {
+ success = ((st.st_mode & S_IWRITE) != 0);
+ }
+ }
+ g_free(filename);
+ filename = nullptr;
+ } else {
+ g_warning( "Unable to convert filename in IO:file_test" );
+ }
+ }
+
+ return success;
+}
+
+/**Checks if directory of file exists, useful
+ * because inkscape doesn't create directories.*/
+bool Inkscape::IO::file_directory_exists( char const *utf8name ){
+ bool exists = true;
+
+ if ( utf8name) {
+ gchar *filename = nullptr;
+ if (utf8name && !g_utf8_validate(utf8name, -1, nullptr)) {
+ /* FIXME: Trying to guess whether or not a filename is already in utf8 is unreliable.
+ If any callers pass non-utf8 data (e.g. using g_get_home_dir), then change caller to
+ use simple g_file_test. Then add g_return_val_if_fail(g_utf_validate(...), false)
+ to beginning of this function. */
+ filename = g_strdup(utf8name);
+ // Looks like g_get_home_dir isn't safe.
+ //g_warning("invalid UTF-8 detected internally. HUNT IT DOWN AND KILL IT!!!");
+ } else {
+ filename = g_filename_from_utf8 ( utf8name, -1, nullptr, nullptr, nullptr );
+ }
+ if ( filename ) {
+ gchar *dirname = g_path_get_dirname(filename);
+ exists = Inkscape::IO::file_test( dirname, G_FILE_TEST_EXISTS);
+ g_free(filename);
+ g_free(dirname);
+ filename = nullptr;
+ dirname = nullptr;
+ } else {
+ g_warning( "Unable to convert filename in IO:file_test" );
+ }
+ }
+
+ return exists;
+
+}
+
+/** Wrapper around g_dir_open, but taking a utf8name as first argument. */
+GDir *
+Inkscape::IO::dir_open(gchar const *const utf8name, guint const flags, GError **const error)
+{
+ gchar *const opsys_name = g_filename_from_utf8(utf8name, -1, nullptr, nullptr, error);
+ if (opsys_name) {
+ GDir *ret = g_dir_open(opsys_name, flags, error);
+ g_free(opsys_name);
+ return ret;
+ } else {
+ return nullptr;
+ }
+}
+
+/**
+ * Like g_dir_read_name, but returns a utf8name (which must be freed, unlike g_dir_read_name).
+ *
+ * N.B. Skips over any dir entries that fail to convert to utf8.
+ */
+gchar *
+Inkscape::IO::dir_read_utf8name(GDir *dir)
+{
+ for (;;) {
+ gchar const *const opsys_name = g_dir_read_name(dir);
+ if (!opsys_name) {
+ return nullptr;
+ }
+ gchar *utf8_name = g_filename_to_utf8(opsys_name, -1, nullptr, nullptr, nullptr);
+ if (utf8_name) {
+ return utf8_name;
+ }
+ }
+}
+
+
+gchar* Inkscape::IO::locale_to_utf8_fallback( const gchar *opsysstring,
+ gssize len,
+ gsize *bytes_read,
+ gsize *bytes_written,
+ GError **error )
+{
+ gchar *result = nullptr;
+ if ( opsysstring ) {
+ gchar *newFileName = g_locale_to_utf8( opsysstring, len, bytes_read, bytes_written, error );
+ if ( newFileName ) {
+ if ( !g_utf8_validate(newFileName, -1, nullptr) ) {
+ g_warning( "input filename did not yield UTF-8" );
+ g_free( newFileName );
+ } else {
+ result = newFileName;
+ }
+ newFileName = nullptr;
+ } else if ( g_utf8_validate(opsysstring, -1, nullptr) ) {
+ // This *might* be a case that we want
+ // g_warning( "input failed filename->utf8, fell back to original" );
+ // TODO handle cases when len >= 0
+ result = g_strdup( opsysstring );
+ } else {
+ gchar const *charset = nullptr;
+ g_get_charset(&charset);
+ g_warning( "input filename conversion failed for file with locale charset '%s'", charset );
+ }
+ }
+ return result;
+}
+
+void
+Inkscape::IO::spawn_async_with_pipes( const std::string& working_directory,
+ const Glib::ArrayHandle<std::string>& argv,
+ Glib::SpawnFlags flags,
+ const sigc::slot<void>& child_setup,
+ Glib::Pid* child_pid,
+ int* standard_input,
+ int* standard_output,
+ int* standard_error)
+{
+ Glib::spawn_async_with_pipes(working_directory,
+ argv,
+ flags,
+ child_setup,
+ child_pid,
+ standard_input,
+ standard_output,
+ standard_error);
+}
+
+
+gchar* Inkscape::IO::sanitizeString( gchar const * str )
+{
+ gchar *result = nullptr;
+ if ( str ) {
+ if ( g_utf8_validate(str, -1, nullptr) ) {
+ result = g_strdup(str);
+ } else {
+ guchar scratch[8];
+ Glib::ustring buf;
+ guchar const *ptr = (guchar const*)str;
+ while ( *ptr )
+ {
+ if ( *ptr == '\\' )
+ {
+ buf.append("\\\\");
+ } else if ( *ptr < 0x80 ) {
+ buf += (char)(*ptr);
+ } else {
+ g_snprintf((gchar*)scratch, sizeof(scratch), "\\x%02x", *ptr);
+ buf.append((const char*)scratch);
+ }
+ ptr++;
+ }
+ result = g_strdup(buf.c_str());
+ }
+ }
+ return result;
+}
+
+/*
+ * Returns the file extension of a path/filename
+ */
+Glib::ustring Inkscape::IO::get_file_extension(Glib::ustring path)
+{
+ Glib::ustring::size_type period_location = path.find_last_of(".");
+ return path.substr(period_location);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/io/sys.h b/src/io/sys.h
new file mode 100644
index 0000000..f159ac6
--- /dev/null
+++ b/src/io/sys.h
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SYS_H
+#define SEEN_SYS_H
+
+/*
+ * System abstraction utility routines
+ *
+ * Authors:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2004-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstdio>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <glib.h>
+#include <glibmm/spawn.h>
+#include <string>
+
+/*#####################
+## U T I L I T Y
+#####################*/
+
+namespace Inkscape {
+namespace IO {
+
+void dump_fopen_call( char const *utf8name, char const *id );
+
+FILE *fopen_utf8name( char const *utf8name, char const *mode );
+
+int mkdir_utf8name( char const *utf8name );
+
+bool file_test( char const *utf8name, GFileTest test );
+
+bool file_directory_exists( char const *utf8name );
+
+bool file_is_writable( char const *utf8name);
+
+GDir *dir_open(gchar const *utf8name, guint flags, GError **error);
+
+gchar *dir_read_utf8name(GDir *dir);
+
+gchar* locale_to_utf8_fallback( const gchar *opsysstring,
+ gssize len,
+ gsize *bytes_read,
+ gsize *bytes_written,
+ GError **error );
+
+gchar* sanitizeString( gchar const * str );
+
+void spawn_async_with_pipes (const std::string& working_directory,
+ const Glib::ArrayHandle<std::string>& argv,
+ Glib::SpawnFlags flags,
+ const sigc::slot<void>& child_setup,
+ Glib::Pid* child_pid,
+ int* standard_input,
+ int* standard_output,
+ int* standard_error);
+
+Glib::ustring get_file_extension(Glib::ustring path);
+
+}
+}
+
+
+#endif // SEEN_SYS_H