diff options
Diffstat (limited to '')
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 |